Skip to content

aragog.phase

phase

A phase defines the equation of state (EOS) and transport properties.

FloatOrArray = float | npt.NDArray module-attribute

logger = logging.getLogger(__name__) module-attribute

CompositePhaseEvaluator(parameters)

Bases: PhaseEvaluatorABC

Evaluates the EOS and transport properties of a composite phase.

This combines the single phase evaluators for the liquid and solid regions with the mixed phase evaluator for the mixed phase region. This ensure that the phase properties are computed correctly for all temperatures and pressures.

Args: parameters: Parameters

Source code in aragog/phase.py
521
522
523
524
525
526
527
528
529
def __init__(self, parameters: Parameters):
    gravitational_acceleration: PropertyProtocol = setup_gravitational_acceleration(parameters)
    self._liquid: PhaseEvaluatorProtocol = SinglePhaseEvaluator(
        parameters.phase_liquid, gravitational_acceleration
    )
    self._solid: PhaseEvaluatorProtocol = SinglePhaseEvaluator(
        parameters.phase_solid, gravitational_acceleration
    )
    self._mixed: MixedPhaseEvaluator = MixedPhaseEvaluator(parameters)

delta_specific_volume()

Difference of specific volume between melt and solid

Source code in aragog/phase.py
638
639
640
641
@override
def delta_specific_volume(self) -> npt.NDArray:
    """Difference of specific volume between melt and solid"""
    return self._delta_specific_volume

heat_capacity()

Heat capacity

Source code in aragog/phase.py
592
593
594
595
@override
def heat_capacity(self) -> npt.NDArray:
    """Heat capacity"""
    return self._heat_capacity

latent_heat()

Latent heat of fusion

Source code in aragog/phase.py
608
609
610
611
@override
def latent_heat(self) -> FloatOrArray:
    """Latent heat of fusion"""
    return self._mixed.latent_heat()

melt_fraction()

Melt fraction

Source code in aragog/phase.py
603
604
605
606
@override
def melt_fraction(self) -> npt.NDArray:
    """Melt fraction"""
    return self._mixed.melt_fraction()

relative_velocity()

Relative velocity between melt and solid

Source code in aragog/phase.py
633
634
635
636
@override
def relative_velocity(self) -> npt.NDArray:
    """Relative velocity between melt and solid"""
    return self._relative_velocity

set_pressure(pressure)

Sets pressure and updates quantities that only depend on pressure

Source code in aragog/phase.py
538
539
540
541
542
543
544
@override
def set_pressure(self, pressure: npt.NDArray) -> None:
    """Sets pressure and updates quantities that only depend on pressure"""
    super().set_pressure(pressure)
    self._solid.set_pressure(pressure)
    self._liquid.set_pressure(pressure)
    self._mixed.set_pressure(pressure)

thermal_conductivity()

Thermal conductivity

Source code in aragog/phase.py
619
620
621
622
@override
def thermal_conductivity(self) -> npt.NDArray:
    """Thermal conductivity"""
    return self._thermal_conductivity

thermal_expansivity()

Thermal expansivity

Source code in aragog/phase.py
624
625
626
627
@override
def thermal_expansivity(self) -> npt.NDArray:
    """Thermal expansivity"""
    return self._thermal_expansivity

viscosity()

Viscosity

Source code in aragog/phase.py
629
630
631
def viscosity(self) -> npt.NDArray:
    """Viscosity"""
    return self._viscosity

ConstantProperty(name, *, value) dataclass

Bases: PropertyProtocol

A property with a constant value

Args: name: Name of the property value: The constant value

Attributes: name: Name of the property value: The constant value ndim: Number of dimensions, which is equal to zero for a constant property

LookupProperty1D(name, *, value) dataclass

Bases: PropertyProtocol

A property from a 1-D lookup

Args: name: Name of the property value: A 2-D array, with x values in the first column and y values in the second column.

Attributes: name: Name of the property value: A 2-D array ndim: Number of dimensions, which is equal to one for a 1-D lookup

gradient(pressure)

Computes the gradient

Source code in aragog/phase.py
114
115
116
def gradient(self, pressure: npt.NDArray) -> npt.NDArray:
    """Computes the gradient"""
    return np.interp(pressure, self.value[:, 0], self._gradient)

LookupProperty2D(name, *, value) dataclass

Bases: PropertyProtocol

A property from a 2-D lookup

Args: name: Name of the property value: The 2-D array

Attributes: name: Name of the property value: The 2-D array ndim: Number of dimensions, which is equal to two for a 2-D lookup

prepare_data_for_spline(data)

Ensure your data is on a regular grid for RectBivariateSpline

Source code in aragog/phase.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def prepare_data_for_spline(self, data):
    """Ensure your data is on a regular grid for RectBivariateSpline"""
    # Extract x, y, and z values
    x_values = np.unique(data[:, 0])  # Unique pressure values
    y_values = np.unique(data[:, 1])  # Unique temperature values

    # Create a grid for z values
    z_values = np.full((x_values.size, y_values.size), np.nan)

    # Find the indices of the x and y values in the unique arrays
    x_indices = np.searchsorted(x_values, data[:, 0])
    y_indices = np.searchsorted(y_values, data[:, 1])

    # Fill the z_values grid
    z_values[x_indices, y_indices] = data[:, 2]

    return x_values, y_values, z_values

MixedPhaseEvaluator(parameters)

Bases: PhaseEvaluatorABC

Evaluates the EOS and transport properties of a mixed phase.

This only computes quantities within the mixed phase region between the solidus and the liquidus. Computing quantities outside of this region will give incorrect results.

Args: parameters: Parameters

Attributes: settings: Mixed phase parameters

Source code in aragog/phase.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def __init__(self, parameters: Parameters):
    gravitational_acceleration: PropertyProtocol = setup_gravitational_acceleration(parameters)
    self.settings: _PhaseMixedParameters = parameters.phase_mixed
    self._liquid: PhaseEvaluatorProtocol = SinglePhaseEvaluator(
        parameters.phase_liquid, gravitational_acceleration
    )
    self._solid: PhaseEvaluatorProtocol = SinglePhaseEvaluator(
        parameters.phase_solid, gravitational_acceleration
    )
    self._solidus: LookupProperty1D = self._get_melting_curve_lookup(
        "solidus", self.settings.solidus
    )
    self._liquidus: LookupProperty1D = self._get_melting_curve_lookup(
        "liquidus", self.settings.liquidus
    )
    self._grain_size: float = self.settings.grain_size
    self._latent_heat: float = self.settings.latent_heat_of_fusion

dTdPs()

TODO: Update reference to sphinx: Solomatov (2007), Treatise on Geophysics, Eq. 3.2

Source code in aragog/interfaces.py
107
108
109
110
111
112
113
def dTdPs(self) -> npt.NDArray:
    """TODO: Update reference to sphinx: Solomatov (2007), Treatise on Geophysics, Eq. 3.2"""
    dTdPs: npt.NDArray = (
        self.thermal_expansivity() * self.temperature / (self.density() * self.heat_capacity())
    )

    return dTdPs

delta_specific_volume()

Difference of specific volume between melt and solid

Source code in aragog/phase.py
447
448
449
450
@override
def delta_specific_volume(self) -> npt.NDArray:
    """Difference of specific volume between melt and solid"""
    return self._delta_specific_volume

heat_capacity()

Heat capacity of the mixed phase :cite:p:{Equation 4,}SOLO07

Source code in aragog/phase.py
372
373
374
375
@override
def heat_capacity(self) -> FloatOrArray:
    """Heat capacity of the mixed phase :cite:p:`{Equation 4,}SOLO07`"""
    return self._heat_capacity

latent_heat()

Latent heat of fusion

Source code in aragog/phase.py
400
401
402
403
@override
def latent_heat(self) -> float:
    """Latent heat of fusion"""
    return self._latent_heat

liquidus()

Liquidus

Source code in aragog/phase.py
377
378
379
380
381
382
def liquidus(self) -> npt.NDArray:
    """Liquidus"""
    liquidus: npt.NDArray = self._liquidus.eval(self.pressure)
    logger.debug("liquidus = %s", liquidus)

    return liquidus

liquidus_gradient()

Liquidus gradient

Source code in aragog/phase.py
384
385
386
def liquidus_gradient(self) -> npt.NDArray:
    """Liquidus gradient"""
    return self._liquidus.gradient(self.pressure)

melt_fraction()

Melt fraction of the mixed phase

The melt fraction is always between zero and one.

Source code in aragog/phase.py
392
393
394
395
396
397
398
@override
def melt_fraction(self) -> npt.NDArray:
    """Melt fraction of the mixed phase

    The melt fraction is always between zero and one.
    """
    return self._melt_fraction

melt_fraction_no_clip()

Melt fraction without clipping

Source code in aragog/phase.py
388
389
390
def melt_fraction_no_clip(self) -> npt.NDArray:
    """Melt fraction without clipping"""
    return self._melt_fraction_no_clip

porosity()

Porosity of the mixed phase, that is the volume fraction occupied by the melt

Source code in aragog/phase.py
405
406
407
def porosity(self) -> npt.NDArray:
    """Porosity of the mixed phase, that is the volume fraction occupied by the melt"""
    return self._porosity

relative_velocity()

Relative velocity between melt and solid

Source code in aragog/phase.py
432
433
434
435
@override
def relative_velocity(self) -> npt.NDArray:
    """Relative velocity between melt and solid"""
    return self._relative_velocity

set_pressure(pressure)

Sets pressure and updates quantities that only depend on pressure

Source code in aragog/phase.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
@override
def set_pressure(self, pressure: npt.NDArray) -> None:
    """Sets pressure and updates quantities that only depend on pressure"""
    super().set_pressure(pressure)
    # Sets the temperature of the solid and liquid phases to the appropriate melting curve in
    # order to evaluate mixed properties
    self._solid.set_temperature(self.solidus())
    self._solid.set_pressure(pressure)
    self._liquid.set_temperature(self.liquidus())
    self._liquid.set_pressure(pressure)
    self._delta_density = self._solid.density() - self._liquid.density()
    self._delta_specific_volume = 1.0/self._liquid.density() - 1.0/self._solid.density()
    self._delta_fusion = self.liquidus() - self.solidus()
    # Heat capacity of the mixed phase :cite:p:`{Equation 4,}SOLO07`
    self._heat_capacity = self.latent_heat() / self.delta_fusion()

set_temperature(temperature)

Sets the temperature.

Source code in aragog/interfaces.py
91
92
93
94
def set_temperature(self, temperature: npt.NDArray) -> None:
    """Sets the temperature."""
    logger.debug("set_temperature = %s", temperature)
    self.temperature = temperature

solidus()

Solidus

Source code in aragog/phase.py
409
410
411
412
413
414
def solidus(self) -> npt.NDArray:
    """Solidus"""
    solidus: npt.NDArray = self._solidus.eval(self.pressure)
    logger.debug("solidus = %s", solidus)

    return solidus

solidus_gradient()

Solidus gradient

Source code in aragog/phase.py
416
417
418
def solidus_gradient(self) -> npt.NDArray:
    """Solidus gradient"""
    return self._solidus.gradient(self.pressure)

MixedPhaseEvaluatorProtocol

Bases: PhaseEvaluatorProtocol, Protocol

Mixed phase evaluator protocol

Parameters(*, boundary_conditions, energy, initial_condition, mesh, phase_solid, phase_liquid, phase_mixed, radionuclides, scalings, solver) dataclass

Assembles all the parameters.

The parameters in each section are scaled here to ensure that all the parameters are scaled (non-dimensionalised) consistently with each other.

from_file(*filenames) classmethod

Parses the parameters in a configuration file(s)

Args: *filenames: Filenames of the configuration data

Source code in aragog/parser.py
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
@classmethod
def from_file(cls, *filenames) -> Self:
    """Parses the parameters in a configuration file(s)

    Args:
        *filenames: Filenames of the configuration data
    """
    parser: ConfigParser = ConfigParser()
    parser.read(*filenames)

    init_dict: dict[str, Any] = {}
    for section_name, dataclass_ in _get_dataclass_from_section_name().items():
        init_dict[section_name] = parser.parse_section(
            using_dataclass=dataclass_, section_name=section_name
        )
    radionuclides: list[_Radionuclide] = []
    for radionuclide_section in cls.radionuclide_sections(parser):
        radionuclide = parser.parse_section(
            using_dataclass=_Radionuclide, section_name=radionuclide_section
        )
        radionuclides.append(radionuclide)

    init_dict["radionuclides"] = radionuclides

    return cls(**init_dict)  # Unpacking gives required arguments so pylint: disable=E1125

radionuclide_sections(parser) staticmethod

Section names relating to radionuclides

Sections relating to radionuclides must have the prefix radionuclide_

Source code in aragog/parser.py
499
500
501
502
503
504
505
506
507
508
509
@staticmethod
def radionuclide_sections(parser: ConfigParser) -> list[str]:
    """Section names relating to radionuclides

    Sections relating to radionuclides must have the prefix radionuclide_
    """
    return [
        parser[section].name
        for section in parser.sections()
        if section.startswith("radionuclide_")
    ]

PhaseEvaluatorABC

Bases: ABC

Phase evaluator ABC

dTdPs()

TODO: Update reference to sphinx: Solomatov (2007), Treatise on Geophysics, Eq. 3.2

Source code in aragog/interfaces.py
107
108
109
110
111
112
113
def dTdPs(self) -> npt.NDArray:
    """TODO: Update reference to sphinx: Solomatov (2007), Treatise on Geophysics, Eq. 3.2"""
    dTdPs: npt.NDArray = (
        self.thermal_expansivity() * self.temperature / (self.density() * self.heat_capacity())
    )

    return dTdPs

set_pressure(pressure)

Sets the pressure.

Source code in aragog/interfaces.py
96
97
98
99
def set_pressure(self, pressure: npt.NDArray) -> None:
    """Sets the pressure."""
    logger.debug("set_pressure = %s", pressure)
    self.pressure = pressure

set_temperature(temperature)

Sets the temperature.

Source code in aragog/interfaces.py
91
92
93
94
def set_temperature(self, temperature: npt.NDArray) -> None:
    """Sets the temperature."""
    logger.debug("set_temperature = %s", temperature)
    self.temperature = temperature

update()

Updates quantities to avoid repeat, possibly expensive, calculations.

Source code in aragog/interfaces.py
101
102
def update(self) -> None:
    """Updates quantities to avoid repeat, possibly expensive, calculations."""

PhaseEvaluatorCollection(parameters) dataclass

A collection of phase evaluators

Creates the phase evaluators and selects the active phase based on configuration data.

Args: parameters: Parameters

Attributes: liquid: Liquid evaluator solid: Solid evaluator mixed: Mixed evaluator composite: Composite evaluator active: The active evaluator, which is defined by configuration data

PhaseEvaluatorProtocol

Bases: Protocol

Phase evaluator protocol

PropertyProtocol

Bases: Protocol

Property protocol

SinglePhaseEvaluator(settings, gravitational_acceleration)

Bases: PhaseEvaluatorABC

Contains the objects to evaluate the EOS and transport properties of a phase.

Args: settings: Phase parameters gravitational_acceleration: PropertyProtocol

Source code in aragog/phase.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def __init__(self, settings: _PhaseParameters, gravitational_acceleration: PropertyProtocol):
    self._settings: _PhaseParameters = settings
    cls_fields: tuple[Field, ...] = fields(self._settings)
    for field_ in cls_fields:
        name: str = field_.name
        private_name: str = f"_{name}"
        value = getattr(self._settings, field_.name)

        if is_number(value):
            # Numbers have already been scaled by the parser
            setattr(self, private_name, ConstantProperty(name=name, value=value))

        elif is_file(value):
            with open(value, encoding="utf-8") as infile:
                logger.debug("%s is a file = %s", name, value)
                header = infile.readline()
                col_names = header[1:].split()
            value_array: npt.NDArray = np.loadtxt(value, ndmin=2)
            logger.debug("before scaling, value_array = %s", value_array)
            # Scale lookup data
            for nn, col_name in enumerate(col_names):
                logger.info("Scaling %s from %s", col_name, value)
                value_array[:, nn] /= getattr(self._settings.scalings_, col_name)
            logger.debug("after scaling, value_array = %s", value_array)
            ndim = value_array.shape[1]
            logger.debug("ndim = %d", ndim)
            if ndim == 2:
                setattr(self, private_name, LookupProperty1D(name=name, value=value_array))
            elif ndim == 3:
                setattr(self, private_name, LookupProperty2D(name=name, value=value_array))
            else:
                raise ValueError(f"Lookup data must have 2 or 3 dimensions, not {ndim}")
        else:
            logger.info("Cannot interpret value (%s): not a number or a file", value)

    self._gravitational_acceleration = gravitational_acceleration

dTdPs()

TODO: Update reference to sphinx: Solomatov (2007), Treatise on Geophysics, Eq. 3.2

Source code in aragog/interfaces.py
107
108
109
110
111
112
113
def dTdPs(self) -> npt.NDArray:
    """TODO: Update reference to sphinx: Solomatov (2007), Treatise on Geophysics, Eq. 3.2"""
    dTdPs: npt.NDArray = (
        self.thermal_expansivity() * self.temperature / (self.density() * self.heat_capacity())
    )

    return dTdPs

set_pressure(pressure)

Sets the pressure.

Source code in aragog/interfaces.py
96
97
98
99
def set_pressure(self, pressure: npt.NDArray) -> None:
    """Sets the pressure."""
    logger.debug("set_pressure = %s", pressure)
    self.pressure = pressure

set_temperature(temperature)

Sets the temperature.

Source code in aragog/interfaces.py
91
92
93
94
def set_temperature(self, temperature: npt.NDArray) -> None:
    """Sets the temperature."""
    logger.debug("set_temperature = %s", temperature)
    self.temperature = temperature

update()

Updates quantities to avoid repeat, possibly expensive, calculations.

Source code in aragog/interfaces.py
101
102
def update(self) -> None:
    """Updates quantities to avoid repeat, possibly expensive, calculations."""

_PhaseMixedParameters(latent_heat_of_fusion, rheological_transition_melt_fraction, rheological_transition_width, solidus, liquidus, phase, phase_transition_width, grain_size) dataclass

Stores settings in the phase_mixed section in the configuration data.

scale_attributes(scalings)

Scales the attributes

Args: scalings: scalings

Source code in aragog/parser.py
323
324
325
326
327
328
329
330
331
def scale_attributes(self, scalings: _ScalingsParameters) -> None:
    """Scales the attributes

    Args:
        scalings: scalings
    """
    self.scalings_ = scalings
    self.latent_heat_of_fusion /= self.scalings_.latent_heat_per_mass
    self.grain_size /= self.scalings_.radius

_PhaseParameters(density, heat_capacity, melt_fraction, thermal_conductivity, thermal_expansivity, viscosity) dataclass

Stores settings in a phase section in the configuration data.

This is used to store settings from phase_liquid and phase_solid.

scale_attributes(scalings)

Scales the attributes if they are numbers.

Args: scalings: scalings

Source code in aragog/parser.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
def scale_attributes(self, scalings: _ScalingsParameters) -> None:
    """Scales the attributes if they are numbers.

    Args:
        scalings: scalings
    """
    self.scalings_ = scalings
    cls_fields: tuple[Field, ...] = fields(self.__class__)
    for field_ in cls_fields:
        value: Any = getattr(self, field_.name)
        try:
            scaling: float = getattr(self.scalings_, field_.name)
            scaled_value = value / scaling
            setattr(self, field_.name, scaled_value)
            logger.info(
                "%s is a number (value = %s, scaling = %s, scaled_value = %s)",
                field_.name,
                value,
                scaling,
                scaled_value,
            )
        except AttributeError:
            logger.info("No scaling found for %s", field_.name)
        except TypeError:
            logger.info(
                "%s is a string (path to a filename) so the data will be scaled later",
                field_.name,
            )

combine_properties(weight, property1, property2)

Linear weighting of two quantities.

Args: weight: The weight to apply to property1 property1: The value of the first property property2: The value of the second property

Returns: The combined (weighted) property

Source code in aragog/utilities.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def combine_properties(weight: Any, property1: Any, property2: Any) -> Any:
    """Linear weighting of two quantities.

    Args:
        weight: The weight to apply to property1
        property1: The value of the first property
        property2: The value of the second property

    Returns:
        The combined (weighted) property
    """
    out = weight * property1 + (1.0 - weight) * property2

    return out

is_file(value)

Checks if value is a file.

Args: value: Object to be checked

Returns: True if the value is a file, otherwise False

Source code in aragog/utilities.py
50
51
52
53
54
55
56
57
58
59
60
61
62
def is_file(value: Any) -> bool:
    """Checks if value is a file.

    Args:
        value: Object to be checked

    Returns:
        True if the value is a file, otherwise False
    """
    if isinstance(value, (str, Path)):
        return Path(value).is_file()

    return False

is_number(value)

Checks if value is a number.

Args: value: Object to be checked

Returns: True if the value is a number, otherwise False

Source code in aragog/utilities.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def is_number(value: Any) -> bool:
    """Checks if value is a number.

    Args:
        value: Object to be checked

    Returns:
        True if the value is a number, otherwise False
    """
    try:
        float(value)
        return True

    except (TypeError, ValueError):
        return False

setup_gravitational_acceleration(parameters)

Sets up the gravitational acceleration property.

Args: parameters: Parameters

Returns: gravitational_acceleration: PropertyProtocol

Source code in aragog/phase.py
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
def setup_gravitational_acceleration(parameters: Parameters):
    """Sets up the gravitational acceleration property.

    Args:
        parameters: Parameters

    Returns:
        gravitational_acceleration: PropertyProtocol
    """
    if parameters.mesh.eos_method == 1:
        # AdamsWilliamson EOS method = constant gravitational acceleration
        gravitational_acceleration = ConstantProperty(
            "gravitational_acceleration", value=parameters.mesh.gravitational_acceleration
        )
    elif parameters.mesh.eos_method == 2:
        # User defined EOS method = space dependent gravitational acceleration (1D lookup)
        # Re-interpolate EOS-data to match standard lookup pressure grid
        interp_gravity = PchipInterpolator(
            np.flip(parameters.mesh.eos_pressure),
            np.flip(parameters.mesh.eos_gravity))
        num_points: int = 138
        max_pressure: float = 1.37e11 / parameters.scalings.pressure
        lookup_data: npt.NDArray = np.zeros((num_points, 2), dtype=float)
        lookup_data[:,0] = np.linspace(0.0, max_pressure, num=num_points)
        lookup_data[:,1] = interp_gravity(lookup_data[:,0])
        gravitational_acceleration = LookupProperty1D(
            "gravitational_acceleration",
            value=lookup_data,
        )
    else:
        raise ValueError(f"EOS method = {parameters.mesh.eos_method} is not a valid selection")

    return gravitational_acceleration

tanh_weight(value, threshold, width)

Computes the tanh weight for viscosity profile and smoothing.

Args: value: Value threshold: Threshold value width: Width of smoothing

Returns: weight

Source code in aragog/utilities.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def tanh_weight(value: FloatOrArray, threshold: float, width: float) -> npt.NDArray:
    """Computes the tanh weight for viscosity profile and smoothing.

    Args:
        value: Value
        threshold: Threshold value
        width: Width of smoothing

    Returns:
        weight
    """
    arg: FloatOrArray = (value - threshold) / width
    weight: npt.NDArray = 0.5 * (1.0 + np.tanh(arg))

    return weight