Skip to content

API Reference

Bases: BaseAquifer

Deterministic aquifer with fixed reservoir properties.

Used as input to ThermoGISDoublet when all reservoir properties are known values (scalars or spatial arrays).

Parameters:

Name Type Description Default
thickness float or array

Aquifer thickness [m]. Must be >= 0.

required
permeability float or array

Aquifer permeability [mD]. Must be >= 0. Either permeability or transmissivity must be provided. If only permeability is given, transmissivity is computed as permeability * thickness.

required
transmissivity float or array

Aquifer transmissivity [mD m]. Must be >= 0. Takes precedence over permeability if both are supplied.

required
porosity float or array

Aquifer porosity [-], between 0 and 1.

required
ntg float or array

Net-to-gross ratio [-], between 0 and 1.

required
depth float or array

Aquifer depth [m].

required
temperature float or array

Reservoir temperature [°C]. If not provided, it is calculated from depth using the temperature gradient and surface temperature in UTCSettings when passed to ThermoGISDoublet.

required
mask float or array

Mask array. Cells where the mask is not NaN are excluded from the simulation. Defaults to NaN (no masking).

required
Source code in pythermogis/aquifer.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
class Aquifer(BaseAquifer):
    """Deterministic aquifer with fixed reservoir properties.

    Used as input to ``ThermoGISDoublet`` when all reservoir properties are
    known values (scalars or spatial arrays).

    Parameters
    ----------
    thickness : float or array
        Aquifer thickness [m]. Must be >= 0.
    permeability : float or array, optional
        Aquifer permeability [mD]. Must be >= 0. Either ``permeability`` or
        ``transmissivity`` must be provided. If only ``permeability`` is given,
        transmissivity is computed as ``permeability * thickness``.
    transmissivity : float or array, optional
        Aquifer transmissivity [mD m]. Must be >= 0. Takes precedence over
        ``permeability`` if both are supplied.
    porosity : float or array
        Aquifer porosity [-], between 0 and 1.
    ntg : float or array
        Net-to-gross ratio [-], between 0 and 1.
    depth : float or array
        Aquifer depth [m].
    temperature : float or array, optional
        Reservoir temperature [°C]. If not provided, it is calculated from
        ``depth`` using the temperature gradient and surface temperature in
        ``UTCSettings`` when passed to ``ThermoGISDoublet``.
    mask : float or array, optional
        Mask array. Cells where the mask is not ``NaN`` are excluded from the
        simulation. Defaults to ``NaN`` (no masking).
    """

    thickness: FloatOrArray
    permeability: FloatOrArray | None = None
    transmissivity: FloatOrArray | None = None

    @model_validator(mode="after")
    def check_permeability_or_transmissivity(self) -> Aquifer:
        has_perm = self.permeability is not None
        has_trans = self.transmissivity is not None

        if not has_perm and not has_trans:
            raise ValueError(
                "Aquifer must provide either permeability or transmissivity; "
                "currently neither are provided"
            )

        if has_perm and has_trans:
            logger.warning(
                "Both permeability and transmissivity provided; however the "
                "doublet simulation will use only transmissivity and ignore "
                "permeability (transmissivity = permeability * thickness)"
            )

        return self

    @field_validator("thickness", "permeability", "transmissivity", mode="after")
    @classmethod
    def check_non_negative(
        cls, value: FloatOrArray | None, info
    ) -> FloatOrArray | None:
        if value is None:
            return value
        if isinstance(value, xr.DataArray):
            if (value < 0.0).any():
                raise ValueError(
                    f"{info.field_name} must always be >= 0.0; "
                    f"negative values are not allowed"
                )
        elif value < 0.0:
            raise ValueError(
                f"{info.field_name} must always be >= 0.0; "
                f"negative values are not allowed"
            )
        return value

    @model_validator(mode="after")
    def calculate_transmissivity(self) -> Aquifer:
        if self.transmissivity is None:
            self.transmissivity = self.permeability * self.thickness
        return self

    def calculate_temperature(
        self, temperature_gradient: float, surface_temperature: float
    ) -> None:
        self.temperature = calculate_temperature_from_gradient(
            self.depth, self.thickness, temperature_gradient, surface_temperature
        )