Skip to content

Fields

Field realizations shared by the physics packages: phase fields, free energies, and tensor transports.

LandauEnergyInteraction(label, *, target, state='phi', kappa=1.0, conserved=False)

Bases: GradientInteraction

Gradient flow of a field phi in the Landau free energy of a double well with a cost of gradients.

The free energy is F = integral -phi^2 / 2 + phi^4 / 4 + kappa |grad phi|^2 / 2. Its functional derivative is the chemical potential mu = -phi + phi^3 - kappa laplacian(phi). A non-conserved field relaxes as phi_t = -mu, the Allen-Cahn equation of model A, and a conserved field as phi_t = laplacian(mu), the Cahn-Hilliard equation of model B, whose total stays constant.

Parameters:

Name Type Description Default
label str

The label of the interaction, unique among the parts of its system.

required
target Annotated[str, RoleSpec()]

The path of the field.

required
state str

The name of the state of the field that holds the order parameter.

'phi'
kappa Annotated[float, ParameterSpec(domain=open(0, oo))]

The cost of gradients, the square of the width of the interfaces.

1.0
conserved bool

Whether the order parameter is conserved.

False

Examples:

>>> from types import SimpleNamespace
>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.models.physics.common.fields import LandauEnergyInteraction
>>> energy = LandauEnergyInteraction('energy', target='mixture').check().prepare()
>>> namespace = SimpleNamespace(target=SimpleNamespace(phi=var('phi')), kappa=var('kappa'))
>>> [law.value for law in energy.run(namespace).laws_]
[-phi^3 + kappa*laplacian(phi) + phi]
Source code in src/skcomplex/models/physics/common/fields/_landau_energy.py
51
52
53
54
55
56
57
58
59
60
61
62
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    state: str = 'phi',
    kappa: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    conserved: bool = False,
) -> None:
    super().__init__(label, target=target, states=state, variational=True, conserved=conserved)
    self.state = state
    self.kappa = kappa

PhaseFieldElement(label, visual=None, *, phi=0.0)

Bases: Element

Site of a phase field, which holds the order parameter phi that free energies and noise move.

Parameters:

Name Type Description Default
label str

The label of the site or the field.

required
visual VisualSpec | None

How the site is drawn, or None for default_visual_.

None
phi Annotated[float, StateSpec()]

The initial order parameter.

0.0

Attributes:

Name Type Description
default_visual_

The visual of the site when it sets none.

Examples:

>>> from skcomplex.models.physics.common.fields import PhaseFieldElement
>>> PhaseFieldElement('site', phi=0.5).phi
0.5
Source code in src/skcomplex/models/physics/common/fields/_phase_field.py
35
36
37
38
39
40
41
42
43
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    phi: Annotated[float, StateSpec()] = 0.0,
) -> None:
    super().__init__(label, visual=visual)
    self.phi = phi

SwiftHohenbergEnergyInteraction(label, *, target, state='u', r=0.3, g=0.0)

Bases: GradientInteraction

Gradient flow u_t = r u - (1 + laplacian)^2 u + g u^2 - u^3 of a field in the Swift-Hohenberg free energy.

The free energy is F = integral -r u^2 / 2 + ((1 + laplacian) u)^2 / 2 - g u^3 / 3 + u^4 / 4. Above onset, r > 0, the modes of wavenumber near 1 grow into rolls, or into hexagons for a large enough quadratic term g.

Parameters:

Name Type Description Default
label str

The label of the interaction, unique among the parts of its system.

required
target Annotated[str, RoleSpec()]

The path of the field.

required
state str

The name of the state of the field.

'u'
r Annotated[float, ParameterSpec()]

The distance above onset.

0.3
g Annotated[float, ParameterSpec()]

The strength of the quadratic term, which breaks the symmetry u -> -u.

0.0

Examples:

>>> from types import SimpleNamespace
>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.models.physics.common.fields import SwiftHohenbergEnergyInteraction
>>> energy = SwiftHohenbergEnergyInteraction('energy', target='cells').check().prepare()
>>> namespace = SimpleNamespace(target=SimpleNamespace(u=var('u')), r=var('r'), g=0)
>>> [law.value for law in energy.run(namespace).laws_]
[-u^3 + r*u - u - 2*laplacian(u) - laplacian(laplacian(u))]
Source code in src/skcomplex/models/physics/common/fields/_swift_hohenberg_energy.py
48
49
50
51
52
53
54
55
56
57
58
59
60
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    state: str = 'u',
    r: Annotated[float, ParameterSpec()] = 0.3,
    g: Annotated[float, ParameterSpec()] = 0.0,
) -> None:
    super().__init__(label, target=target, states=state, variational=True)
    self.state = state
    self.r = r
    self.g = g

corotational(value, velocity, xi=0)

Return the transport of a symmetric tensor or a vector by a flow that rotates and aligns it.

A symmetric tensor T gets the Beris-Edwards transport u.grad(T) - S, where S = (xi E + W)(T + I/d) + (T + I/d)(xi E - W) - 2 xi (T + I/d) tr(T grad(u)), with the strain rate E, the vorticity tensor W, the antisymmetric part of vector_gradient(u), and the dimension d. A vector p gets u.grad(p) - W p - xi E p, so both turn with the flow.

Parameters:

Name Type Description Default
value Matrix | FreeModuleElement

A symmetric square matrix, or a vector.

required
velocity Sequence[object] | FreeModuleElement

One velocity component per dimension.

required
xi object

The flow-alignment parameter.

0

Returns:

Name Type Description
transport Matrix | FreeModuleElement

The matrix or the vector to subtract from the rate of the value.

Raises:

Type Description
TensorShapeError

The value is neither a square matrix nor a vector.

Examples:

>>> from sage.all__sagemath_symbolics import SR, matrix, var
>>> from skcomplex.models.physics.common.fields import corotational
>>> a, b = var('a, b')
>>> corotational(matrix(SR, [[a, b], [b, -a]]), [var('u'), 0]).dimensions()
(2, 2)
Source code in src/skcomplex/models/physics/common/fields/_transports.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def corotational(
    value: Matrix | FreeModuleElement,
    velocity: Sequence[object] | FreeModuleElement,
    xi: object = 0,
) -> Matrix | FreeModuleElement:
    """Return the transport of a symmetric tensor or a vector by a flow that rotates and aligns it.

    A symmetric tensor `T` gets the Beris-Edwards transport `u.grad(T) - S`, where
    `S = (xi E + W)(T + I/d) + (T + I/d)(xi E - W) - 2 xi (T + I/d) tr(T grad(u))`, with the strain rate `E`, the
    vorticity tensor `W`, the antisymmetric part of `vector_gradient(u)`, and the dimension `d`. A vector `p` gets
    `u.grad(p) - W p - xi E p`, so both turn with the flow.

    Args:
        value:
            A symmetric square matrix, or a vector.

        velocity:
            One velocity component per dimension.

        xi:
            The flow-alignment parameter.

    Returns:
        transport:
            The matrix or the vector to subtract from the rate of the value.

    Raises:
        TensorShapeError:
            The value is neither a square matrix nor a vector.

    Examples:
        >>> from sage.all__sagemath_symbolics import SR, matrix, var
        >>> from skcomplex.models.physics.common.fields import corotational
        >>> a, b = var('a, b')
        >>> corotational(matrix(SR, [[a, b], [b, -a]]), [var('u'), 0]).dimensions()
        (2, 2)
    """
    flow = _check_vector('corotational', velocity)
    size = len(flow)
    gradients = vector_gradient(flow)
    strain, vorticity = symmetric_part(gradients), antisymmetric_part(gradients)
    if isinstance(value, FreeModuleElement):
        advected = vector(SR, [_advected(item, flow) for item in value])
        return advected - vorticity * value - SR(xi) * (strain * value)
    tensor = _check_matrix('corotational', value)
    advected_tensor = tensor.apply_map(lambda item: _advected(item, flow))
    shifted_tensor = tensor + identity_matrix(SR, size) / size
    aligned = (SR(xi) * strain + vorticity) * shifted_tensor + shifted_tensor * (SR(xi) * strain - vorticity)
    stretching = 2 * SR(xi) * (tensor * gradients).trace() * shifted_tensor
    return advected_tensor - aligned + stretching

upper_convected(tensor, velocity)

Return the upper-convected transport of a rank-2 tensor, u.grad(T) - grad(u)^T T - T grad(u).

Parameters:

Name Type Description Default
tensor Matrix

A square matrix of expressions.

required
velocity Sequence[object] | FreeModuleElement

One velocity component per dimension.

required

Returns:

Name Type Description
transport Matrix

The matrix to subtract from the rate of the tensor.

Raises:

Type Description
TensorShapeError

The tensor is not a square matrix or the velocity is not a vector.

Examples:

>>> from sage.all__sagemath_symbolics import SR, identity_matrix, var
>>> from skcomplex.models.physics.common.fields import upper_convected
>>> upper_convected(identity_matrix(SR, 2), [var('u'), var('v')])[0, 0]
-2*partial(u, 0)
Source code in src/skcomplex/models/physics/common/fields/_transports.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def upper_convected(tensor: Matrix, velocity: Sequence[object] | FreeModuleElement) -> Matrix:
    """Return the upper-convected transport of a rank-2 tensor, `u.grad(T) - grad(u)^T T - T grad(u)`.

    Args:
        tensor:
            A square matrix of expressions.

        velocity:
            One velocity component per dimension.

    Returns:
        transport:
            The matrix to subtract from the rate of the tensor.

    Raises:
        TensorShapeError:
            The tensor is not a square matrix or the velocity is not a vector.

    Examples:
        >>> from sage.all__sagemath_symbolics import SR, identity_matrix, var
        >>> from skcomplex.models.physics.common.fields import upper_convected
        >>> upper_convected(identity_matrix(SR, 2), [var('u'), var('v')])[0, 0]
        -2*partial(u, 0)
    """
    checked = _check_matrix('upper_convected', tensor)
    flow = _check_vector('upper_convected', velocity)
    gradients = vector_gradient(flow)
    advected = checked.apply_map(lambda item: _advected(item, flow))
    return advected - gradients.transpose() * checked - checked * gradients