Skip to content

polykin.properties.equations¤

This modules implements commonly used equations (correlations) to describe the physical properties of pure components.

Antoine ¤

Antoine equation for vapor pressure.

This equation implements the following temperature dependence:

\[ \log_{base} P^* = A - \frac{B}{T + C} \]

where \(A\), \(B\) and \(C\) are component-specific constants, \(P^*\) is the vapor pressure and \(T\) is the temperature. When \(C=0\), this equation reverts to the Clapeyron equation.

Note

There is no consensus on the value of \(base\), the unit of temperature, or the unit of pressure. The function is flexible enough to accomodate most cases, but care should be taken to ensure the parameters match the intended use.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation. Unit = K.

TYPE: float

C

Parameter of equation. Unit = K.

TYPE: float DEFAULT: 0.0

base10

If True base of logarithm is 10, otherwise it is \(e\).

TYPE: bool DEFAULT: True

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of vapor pressure.

TYPE: str DEFAULT: 'Pa'

symbol

Symbol of vapor pressure.

TYPE: str DEFAULT: 'P^*'

name

Name.

TYPE: str DEFAULT: ''

See also
  • DIPPR101: alternative method, applicable to wider temperature ranges.
  • Wagner: alternative method, applicable to wider temperature ranges.
Source code in src/polykin/properties/equations/vapor_pressure.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 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
 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
class Antoine(PropertyEquationT):
    r"""[Antoine](https://en.wikipedia.org/wiki/Antoine_equation) equation for
    vapor pressure.

    This equation implements the following temperature dependence:

    $$ \log_{base} P^* = A - \frac{B}{T + C} $$

    where $A$, $B$ and $C$ are component-specific constants, $P^*$ is the vapor
    pressure and $T$ is the temperature. When $C=0$, this equation reverts to
    the Clapeyron equation.

    !!! note
        There is no consensus on the value of $base$, the unit of temperature,
        or the unit of pressure. The function is flexible enough to accomodate
        most cases, but care should be taken to ensure the parameters match the
        intended use.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
        Unit = K.
    C : float
        Parameter of equation.
        Unit = K.
    base10 : bool
        If `True` base of logarithm is `10`, otherwise it is $e$.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of vapor pressure.
    symbol : str
        Symbol of vapor pressure.
    name : str
        Name.

    See also
    --------
    * [`DIPPR101`](./#polykin.properties.equations.dippr.DIPPR101):
      alternative method, applicable to wider temperature ranges.
    * [`Wagner`](./#polykin.properties.equations.vapor_pressure.Wagner):
      alternative method, applicable to wider temperature ranges.

    """

    _pinfo = {'A': ('', True), 'B': ('K', True), 'C': ('K', True),
              'base10': ('', False)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 base10: bool = True,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = 'Pa',
                 symbol: str = 'P^*',
                 name: str = ''
                 ) -> None:

        self.p = {'A': A, 'B': B, 'C': C, 'base10': base10}
        super().__init__((Tmin, Tmax), unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 base10: bool
                 ) -> Union[float, FloatArray]:
        r"""Antoine equation.

        Parameters
        ----------
        T : float | FloatArray
            Temperature.
            Unit = K.
        A : float
            Parameter of equation.
        B : float
            Parameter of equation.
        C : float
            Parameter of equation.
        base10 : bool
            If `True` base of logarithm is `10`, otherwise it is $e$.

        Returns
        -------
        float | FloatArray
            Vapor pressure. Unit = Any.
        """
        x = A - B/(T + C)
        if base10:
            return 10**x
        else:
            return exp(x)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    base10: bool,
) -> Union[float, FloatArray]

Antoine equation.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: float | FloatArray

A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float

base10

If True base of logarithm is 10, otherwise it is \(e\).

TYPE: bool

RETURNS DESCRIPTION
float | FloatArray

Vapor pressure. Unit = Any.

Source code in src/polykin/properties/equations/vapor_pressure.py
 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
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             base10: bool
             ) -> Union[float, FloatArray]:
    r"""Antoine equation.

    Parameters
    ----------
    T : float | FloatArray
        Temperature.
        Unit = K.
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    base10 : bool
        If `True` base of logarithm is `10`, otherwise it is $e$.

    Returns
    -------
    float | FloatArray
        Vapor pressure. Unit = Any.
    """
    x = A - B/(T + C)
    if base10:
        return 10**x
    else:
        return exp(x)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR100 ¤

DIPPR-100 equation.

This equation implements the following temperature dependence:

\[ Y = A + B T + C T^2 + D T^3 + E T^4 \]

where \(A\) to \(E\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float DEFAULT: 0.0

B

Parameter of equation.

TYPE: float DEFAULT: 0.0

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

E

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
 71
 72
 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
class DIPPR100(DIPPRP5):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-100 equation.

    This equation implements the following temperature dependence:

    $$ Y = A + B T + C T^2 + D T^3 + E T^4 $$

    where $A$ to $E$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('#/K', True), 'C': ('#/K²', True),
              'D': ('#/K³', True), 'E': ('#/K⁴', True)}

    def __init__(self,
                 A: float = 0.,
                 B: float = 0.,
                 C: float = 0.,
                 D: float = 0.,
                 E: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, E, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-100 equation."""
        return A + B*T + C*T**2 + D*T**3 + E*T**4

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
) -> Union[float, FloatArray]

DIPPR-100 equation.

Source code in src/polykin/properties/equations/dippr.py
125
126
127
128
129
130
131
132
133
134
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-100 equation."""
    return A + B*T + C*T**2 + D*T**3 + E*T**4

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR101 ¤

DIPPR-101 equation.

This equation implements the following temperature dependence:

\[ Y = \exp{\left(A + B / T + C \ln(T) + D T^E\right)} \]

where \(A\) to \(E\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

E

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
class DIPPR101(DIPPRP5):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-101 equation.

    This equation implements the following temperature dependence:

    $$ Y = \exp{\left(A + B / T + C \ln(T) + D T^E\right)} $$

    where $A$ to $E$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """
    _pinfo = {'A': ('', True), 'B': ('K', True), 'C': ('', True),
              'D': ('', True), 'E': ('', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 E: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, E, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-101 equation."""
        return exp(A + B/T + C*log(T) + D*T**E)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
) -> Union[float, FloatArray]

DIPPR-101 equation.

Source code in src/polykin/properties/equations/dippr.py
190
191
192
193
194
195
196
197
198
199
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-101 equation."""
    return exp(A + B/T + C*log(T) + D*T**E)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR102 ¤

DIPPR-102 equation.

This equation implements the following temperature dependence:

\[ Y = \frac{A T^B}{ 1 + C/T + D/T^2} \]

where \(A\) to \(D\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
class DIPPR102(DIPPRP4):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-102 equation.

    This equation implements the following temperature dependence:

    $$ Y = \frac{A T^B}{ 1 + C/T + D/T^2} $$

    where $A$ to $D$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('', True), 'C': ('K', True),
              'D': ('K²', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-102 equation."""
        return (A * T**B) / (1 + C/T + D/T**2)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
) -> Union[float, FloatArray]

DIPPR-102 equation.

Source code in src/polykin/properties/equations/dippr.py
253
254
255
256
257
258
259
260
261
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-102 equation."""
    return (A * T**B) / (1 + C/T + D/T**2)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR104 ¤

DIPPR-104 equation.

This equation implements the following temperature dependence:

\[ Y = A + B/T + C/T^3 + D/T^8 + E/T^9 \]

where \(A\) to \(E\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

E

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
class DIPPR104(DIPPRP5):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-104 equation.

    This equation implements the following temperature dependence:

    $$ Y = A + B/T + C/T^3 + D/T^8 + E/T^9 $$

    where $A$ to $E$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('#·K', True), 'C': ('#·K³', True),
              'D': ('#·K⁸', True), 'E': ('#·K⁹', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 E: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, E, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-104 equation."""
        return A + B/T + C/T**3 + D/T**8 + E/T**9

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
) -> Union[float, FloatArray]

DIPPR-104 equation.

Source code in src/polykin/properties/equations/dippr.py
318
319
320
321
322
323
324
325
326
327
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-104 equation."""
    return A + B/T + C/T**3 + D/T**8 + E/T**9

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR105 ¤

DIPPR-105 equation.

This equation implements the following temperature dependence:

\[ Y = \frac{A}{B^{ \left( 1 + (1 - T / C)^D \right) }} \]

where \(A\) to \(D\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float

D

Parameter of equation.

TYPE: float

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
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
377
378
379
380
381
382
383
384
385
386
387
388
389
class DIPPR105(DIPPRP4):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-105 equation.

    This equation implements the following temperature dependence:

    $$ Y = \frac{A}{B^{ \left( 1 + (1 - T / C)^D \right) }} $$

    where $A$ to $D$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('', True), 'C': ('K', True),
              'D': ('', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-105 equation."""
        return A / B**(1 + (1 - T / C)**D)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
) -> Union[float, FloatArray]

DIPPR-105 equation.

Source code in src/polykin/properties/equations/dippr.py
381
382
383
384
385
386
387
388
389
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-105 equation."""
    return A / B**(1 + (1 - T / C)**D)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR106 ¤

DIPPR-106 equation.

This equation implements the following temperature dependence:

\[ Y = A (1 - T_r)^{B + C T_r + D T_r^2 + E T_r^3} \]

where \(A\) to \(E\) are component-specific constants, \(T\) is the absolute temperature, \(T_c\) is the critical temperature and \(T_r = T/T_c\) is the reduced temperature.

PARAMETER DESCRIPTION
Tc

Critical temperature. Unit = K.

TYPE: float

A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

E

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
class DIPPR106(DIPPR):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-106 equation.

    This equation implements the following temperature dependence:

    $$ Y = A (1 - T_r)^{B + C T_r + D T_r^2 + E T_r^3} $$

    where $A$ to $E$ are component-specific constants, $T$ is the absolute
    temperature, $T_c$ is the critical temperature and $T_r = T/T_c$ is the
    reduced temperature.

    Parameters
    ----------
    Tc : float
        Critical temperature.
        Unit = K.
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('', True), 'C': ('', True),
              'D': ('', True), 'E': ('', True), 'Tc': ('K', False)}

    def __init__(self,
                 Tc: float,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 E: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        self.p = {'A': A, 'B': B, 'C': C, 'D': D, 'E': E, 'Tc': Tc}
        super().__init__((Tmin, Tmax), unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float,
                 Tc: float,
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-106 equation."""
        Tr = T/Tc
        return A*(1-Tr)**(B + Tr*(C + Tr*(D + E*Tr)))

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
    Tc: float,
) -> Union[float, FloatArray]

DIPPR-106 equation.

Source code in src/polykin/properties/equations/dippr.py
452
453
454
455
456
457
458
459
460
461
462
463
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float,
             Tc: float,
             ) -> Union[float, FloatArray]:
    r"""DIPPR-106 equation."""
    Tr = T/Tc
    return A*(1-Tr)**(B + Tr*(C + Tr*(D + E*Tr)))

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR107 ¤

DIPPR-107 equation.

This equation implements the following temperature dependence:

\[ Y = A + B\left[{\frac {C/T}{\sinh \left(C/T\right)}}\right]^2 + D\left[{\frac {E/T}{\cosh \left(E/T\right)}}\right]^2 \]

where \(A\) to \(E\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float

D

Parameter of equation.

TYPE: float

E

Parameter of equation.

TYPE: float

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
466
467
468
469
470
471
472
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
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
class DIPPR107(DIPPRP5):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-107 equation.

    This equation implements the following temperature dependence:

    $$ Y = A + B\left[{\frac {C/T}{\sinh \left(C/T\right)}}\right]^2 +
        D\left[{\frac {E/T}{\cosh \left(E/T\right)}}\right]^2 $$

    where $A$ to $E$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('#', True), 'C': ('K', True),
              'D': ('#', True), 'E': ('K', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, E, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-107 equation."""
        return A + B*(C/T/sinh(C/T))**2 + D*(E/T/cosh(E/T))**2

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
) -> Union[float, FloatArray]

DIPPR-107 equation.

Source code in src/polykin/properties/equations/dippr.py
521
522
523
524
525
526
527
528
529
530
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-107 equation."""
    return A + B*(C/T/sinh(C/T))**2 + D*(E/T/cosh(E/T))**2

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

Wagner ¤

Wagner equation for vapor pressure.

This equation implements the following temperature dependence:

\[ \ln(P^*/P_c) = \frac{a\tau + b\tau^{1.5} + c\tau^{2.5} + d\tau^5}{T_r}\]

with:

\[ \tau = 1 - T_r\]

where \(a\) to \(d\) are component-specific constants, \(P^*\) is the vapor pressure, \(P_c\) is the critical pressure, \(T\) is the absolute temperature, \(T_c\) is the critical temperature, and \(T_r=T/T_c\) is the reduced temperature.

Note

There are several versions of the Wagner equation with different exponents. This is the so-called 25 version also used in the ThermoData Engine.

PARAMETER DESCRIPTION
Tc

Critical temperature. Unit = K.

TYPE: float

Pc

Critical pressure. Unit = Any.

TYPE: float

a

Parameter of equation.

TYPE: float

b

Parameter of equation.

TYPE: float

c

Parameter of equation.

TYPE: float

d

Parameter of equation.

TYPE: float

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of vapor pressure.

TYPE: str DEFAULT: 'Pa'

symbol

Symbol of vapor pressure.

TYPE: str DEFAULT: 'P^*'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/vapor_pressure.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
226
227
228
229
230
231
232
233
234
235
class Wagner(PropertyEquationT):
    r"""[Wagner](https://de.wikipedia.org/wiki/Wagner-Gleichung) equation for
    vapor pressure.

    This equation implements the following temperature dependence:

    $$ \ln(P^*/P_c) = \frac{a\tau + b\tau^{1.5} + c\tau^{2.5} + d\tau^5}{T_r}$$

    with:

    $$ \tau = 1 - T_r$$

    where $a$ to $d$ are component-specific constants, $P^*$ is the vapor
    pressure, $P_c$ is the critical pressure, $T$ is the absolute temperature,
    $T_c$ is the critical temperature, and $T_r=T/T_c$ is the reduced
    temperature.

    !!! note

        There are several versions of the Wagner equation with different
        exponents. This is the so-called 25 version also used in the
        [ThermoData Engine](https://trc.nist.gov/tde.html).

    Parameters
    ----------
    Tc : float
        Critical temperature.
        Unit = K.
    Pc : float
        Critical pressure.
        Unit = Any.
    a : float
        Parameter of equation.
    b : float
        Parameter of equation.
    c : float
        Parameter of equation.
    d : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of vapor pressure.
    symbol : str
        Symbol of vapor pressure.
    name : str
        Name.
    """

    _pinfo = {'a': ('', True), 'b': ('', True), 'c': ('', True),
              'd': ('', True), 'Pc': ('#', False), 'Tc': ('K', False)}

    def __init__(self,
                 a: float,
                 b: float,
                 c: float,
                 d: float,
                 Pc: float,
                 Tc: float,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = 'Pa',
                 symbol: str = 'P^*',
                 name: str = ''
                 ) -> None:

        self.p = {'a': a, 'b': b, 'c': c, 'd': d, 'Pc': Pc, 'Tc': Tc}
        super().__init__((Tmin, Tmax), unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 a: float,
                 b: float,
                 c: float,
                 d: float,
                 Pc: float,
                 Tc: float,
                 ) -> Union[float, FloatArray]:
        r"""Wagner equation.

        Parameters
        ----------
        T : float | FloatArray
            Temperature. Unit = K.
        a : float
            Parameter of equation.
        b : float
            Parameter of equation.
        c : float
            Parameter of equation.
        d : float
            Parameter of equation.
        Pc : float
            Critical pressure.
            Unit = Any.
        Tc : float
            Critical temperature.
            Unit = K.

        Returns
        -------
        float | FloatArray
            Vapor pressure. Unit = [Pc].
        """
        Tr = T/Tc
        t = 1 - Tr
        return Pc*exp((a*t + b*t**1.5 + c*t**2.5 + d*t**5)/Tr)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    a: float,
    b: float,
    c: float,
    d: float,
    Pc: float,
    Tc: float,
) -> Union[float, FloatArray]

Wagner equation.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: float | FloatArray

a

Parameter of equation.

TYPE: float

b

Parameter of equation.

TYPE: float

c

Parameter of equation.

TYPE: float

d

Parameter of equation.

TYPE: float

Pc

Critical pressure. Unit = Any.

TYPE: float

Tc

Critical temperature. Unit = K.

TYPE: float

RETURNS DESCRIPTION
float | FloatArray

Vapor pressure. Unit = [Pc].

Source code in src/polykin/properties/equations/vapor_pressure.py
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
226
227
228
229
230
231
232
233
234
235
@staticmethod
def equation(T: Union[float, FloatArray],
             a: float,
             b: float,
             c: float,
             d: float,
             Pc: float,
             Tc: float,
             ) -> Union[float, FloatArray]:
    r"""Wagner equation.

    Parameters
    ----------
    T : float | FloatArray
        Temperature. Unit = K.
    a : float
        Parameter of equation.
    b : float
        Parameter of equation.
    c : float
        Parameter of equation.
    d : float
        Parameter of equation.
    Pc : float
        Critical pressure.
        Unit = Any.
    Tc : float
        Critical temperature.
        Unit = K.

    Returns
    -------
    float | FloatArray
        Vapor pressure. Unit = [Pc].
    """
    Tr = T/Tc
    t = 1 - Tr
    return Pc*exp((a*t + b*t**1.5 + c*t**2.5 + d*t**5)/Tr)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

Yaws ¤

Yaws equation for saturated liquid viscosity.

This equation implements the following temperature dependence:

\[ \log_{base} \mu = A + \frac{B}{T} + C T + D T^2 \]

where \(A\) to \(D\) are component-specific constants, \(\mu\) is the liquid viscosity, and \(T\) is the temperature. When \(C=D=0\), this equation reverts to the Andrade equation.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation. Unit = K.

TYPE: float

C

Parameter of equation. Unit = K⁻¹.

TYPE: float DEFAULT: 0.0

D

Parameter of equation. Unit = K⁻².

TYPE: float DEFAULT: 0.0

base10

If True base of logarithm is 10, otherwise it is \(e\).

TYPE: bool DEFAULT: True

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of viscosity.

TYPE: str DEFAULT: 'Pa·s'

symbol

Symbol of viscosity.

TYPE: str DEFAULT: '\\mu'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/viscosity.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 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
 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
class Yaws(PropertyEquationT):
    r"""Yaws equation for saturated liquid viscosity.

    This equation implements the following temperature dependence:

    $$ \log_{base} \mu = A + \frac{B}{T} + C T + D T^2 $$

    where $A$ to $D$ are component-specific constants, $\mu$ is the liquid
    viscosity, and $T$ is the temperature. When $C=D=0$, this equation reverts
    to the Andrade equation.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
        Unit = K.
    C : float
        Parameter of equation.
        Unit = K⁻¹.
    D : float
        Parameter of equation.
        Unit = K⁻².
    base10 : bool
        If `True` base of logarithm is `10`, otherwise it is $e$.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of viscosity.
    symbol : str
        Symbol of viscosity.
    name : str
        Name.
    """

    _pinfo = {'A': ('', True), 'B': ('K', True), 'C': ('K⁻¹', True),
              'D': ('K⁻²', True), 'base10': ('', False)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 base10: bool = True,
                 Tmin: float = 0.,
                 Tmax: float = np.inf,
                 unit: str = 'Pa·s',
                 symbol: str = r'\mu',
                 name: str = ''
                 ) -> None:
        """Construct `Yaws` with the given parameters."""

        self.p = {'A': A, 'B': B, 'C': C, 'D': D, 'base10': base10}
        super().__init__((Tmin, Tmax), unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 base10: bool
                 ) -> Union[float, FloatArray]:
        r"""Yaws equation.

        Parameters
        ----------
        T : float | FloatArray
            Temperature. Unit = K.
        A : float
            Parameter of equation.
        B : float
            Parameter of equation.
            Unit = K.
        C : float
            Parameter of equation.
            Unit = K⁻¹.
        D : float
            Parameter of equation.
            Unit = K⁻².
        base10 : bool
            If `True` base of logarithm is `10`, otherwise it is $e$.

        Returns
        -------
        float | FloatArray
            Viscosity. Unit = Any.
        """
        x = A + B/T + C*T + D*T**2
        if base10:
            return 10**x
        else:
            return exp(x)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

__init__ ¤

__init__(
    A: float,
    B: float,
    C: float = 0.0,
    D: float = 0.0,
    base10: bool = True,
    Tmin: float = 0.0,
    Tmax: float = np.inf,
    unit: str = "Pa·s",
    symbol: str = "\\mu",
    name: str = "",
) -> None

Construct Yaws with the given parameters.

Source code in src/polykin/properties/equations/viscosity.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def __init__(self,
             A: float,
             B: float,
             C: float = 0.,
             D: float = 0.,
             base10: bool = True,
             Tmin: float = 0.,
             Tmax: float = np.inf,
             unit: str = 'Pa·s',
             symbol: str = r'\mu',
             name: str = ''
             ) -> None:
    """Construct `Yaws` with the given parameters."""

    self.p = {'A': A, 'B': B, 'C': C, 'D': D, 'base10': base10}
    super().__init__((Tmin, Tmax), unit, symbol, name)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    base10: bool,
) -> Union[float, FloatArray]

Yaws equation.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: float | FloatArray

A

Parameter of equation.

TYPE: float

B

Parameter of equation. Unit = K.

TYPE: float

C

Parameter of equation. Unit = K⁻¹.

TYPE: float

D

Parameter of equation. Unit = K⁻².

TYPE: float

base10

If True base of logarithm is 10, otherwise it is \(e\).

TYPE: bool

RETURNS DESCRIPTION
float | FloatArray

Viscosity. Unit = Any.

Source code in src/polykin/properties/equations/viscosity.py
 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
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             base10: bool
             ) -> Union[float, FloatArray]:
    r"""Yaws equation.

    Parameters
    ----------
    T : float | FloatArray
        Temperature. Unit = K.
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
        Unit = K.
    C : float
        Parameter of equation.
        Unit = K⁻¹.
    D : float
        Parameter of equation.
        Unit = K⁻².
    base10 : bool
        If `True` base of logarithm is `10`, otherwise it is $e$.

    Returns
    -------
    float | FloatArray
        Viscosity. Unit = Any.
    """
    x = A + B/T + C*T + D*T**2
    if base10:
        return 10**x
    else:
        return exp(x)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

Tutorial

Antoine ¤

Antoine equation for vapor pressure.

This equation implements the following temperature dependence:

\[ \log_{base} P^* = A - \frac{B}{T + C} \]

where \(A\), \(B\) and \(C\) are component-specific constants, \(P^*\) is the vapor pressure and \(T\) is the temperature. When \(C=0\), this equation reverts to the Clapeyron equation.

Note

There is no consensus on the value of \(base\), the unit of temperature, or the unit of pressure. The function is flexible enough to accomodate most cases, but care should be taken to ensure the parameters match the intended use.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation. Unit = K.

TYPE: float

C

Parameter of equation. Unit = K.

TYPE: float DEFAULT: 0.0

base10

If True base of logarithm is 10, otherwise it is \(e\).

TYPE: bool DEFAULT: True

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of vapor pressure.

TYPE: str DEFAULT: 'Pa'

symbol

Symbol of vapor pressure.

TYPE: str DEFAULT: 'P^*'

name

Name.

TYPE: str DEFAULT: ''

See also
  • DIPPR101: alternative method, applicable to wider temperature ranges.
  • Wagner: alternative method, applicable to wider temperature ranges.
Source code in src/polykin/properties/equations/vapor_pressure.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 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
 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
class Antoine(PropertyEquationT):
    r"""[Antoine](https://en.wikipedia.org/wiki/Antoine_equation) equation for
    vapor pressure.

    This equation implements the following temperature dependence:

    $$ \log_{base} P^* = A - \frac{B}{T + C} $$

    where $A$, $B$ and $C$ are component-specific constants, $P^*$ is the vapor
    pressure and $T$ is the temperature. When $C=0$, this equation reverts to
    the Clapeyron equation.

    !!! note
        There is no consensus on the value of $base$, the unit of temperature,
        or the unit of pressure. The function is flexible enough to accomodate
        most cases, but care should be taken to ensure the parameters match the
        intended use.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
        Unit = K.
    C : float
        Parameter of equation.
        Unit = K.
    base10 : bool
        If `True` base of logarithm is `10`, otherwise it is $e$.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of vapor pressure.
    symbol : str
        Symbol of vapor pressure.
    name : str
        Name.

    See also
    --------
    * [`DIPPR101`](./#polykin.properties.equations.dippr.DIPPR101):
      alternative method, applicable to wider temperature ranges.
    * [`Wagner`](./#polykin.properties.equations.vapor_pressure.Wagner):
      alternative method, applicable to wider temperature ranges.

    """

    _pinfo = {'A': ('', True), 'B': ('K', True), 'C': ('K', True),
              'base10': ('', False)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 base10: bool = True,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = 'Pa',
                 symbol: str = 'P^*',
                 name: str = ''
                 ) -> None:

        self.p = {'A': A, 'B': B, 'C': C, 'base10': base10}
        super().__init__((Tmin, Tmax), unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 base10: bool
                 ) -> Union[float, FloatArray]:
        r"""Antoine equation.

        Parameters
        ----------
        T : float | FloatArray
            Temperature.
            Unit = K.
        A : float
            Parameter of equation.
        B : float
            Parameter of equation.
        C : float
            Parameter of equation.
        base10 : bool
            If `True` base of logarithm is `10`, otherwise it is $e$.

        Returns
        -------
        float | FloatArray
            Vapor pressure. Unit = Any.
        """
        x = A - B/(T + C)
        if base10:
            return 10**x
        else:
            return exp(x)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    base10: bool,
) -> Union[float, FloatArray]

Antoine equation.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: float | FloatArray

A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float

base10

If True base of logarithm is 10, otherwise it is \(e\).

TYPE: bool

RETURNS DESCRIPTION
float | FloatArray

Vapor pressure. Unit = Any.

Source code in src/polykin/properties/equations/vapor_pressure.py
 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
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             base10: bool
             ) -> Union[float, FloatArray]:
    r"""Antoine equation.

    Parameters
    ----------
    T : float | FloatArray
        Temperature.
        Unit = K.
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    base10 : bool
        If `True` base of logarithm is `10`, otherwise it is $e$.

    Returns
    -------
    float | FloatArray
        Vapor pressure. Unit = Any.
    """
    x = A - B/(T + C)
    if base10:
        return 10**x
    else:
        return exp(x)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR100 ¤

DIPPR-100 equation.

This equation implements the following temperature dependence:

\[ Y = A + B T + C T^2 + D T^3 + E T^4 \]

where \(A\) to \(E\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float DEFAULT: 0.0

B

Parameter of equation.

TYPE: float DEFAULT: 0.0

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

E

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
 71
 72
 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
class DIPPR100(DIPPRP5):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-100 equation.

    This equation implements the following temperature dependence:

    $$ Y = A + B T + C T^2 + D T^3 + E T^4 $$

    where $A$ to $E$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('#/K', True), 'C': ('#/K²', True),
              'D': ('#/K³', True), 'E': ('#/K⁴', True)}

    def __init__(self,
                 A: float = 0.,
                 B: float = 0.,
                 C: float = 0.,
                 D: float = 0.,
                 E: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, E, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-100 equation."""
        return A + B*T + C*T**2 + D*T**3 + E*T**4

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
) -> Union[float, FloatArray]

DIPPR-100 equation.

Source code in src/polykin/properties/equations/dippr.py
125
126
127
128
129
130
131
132
133
134
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-100 equation."""
    return A + B*T + C*T**2 + D*T**3 + E*T**4

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR101 ¤

DIPPR-101 equation.

This equation implements the following temperature dependence:

\[ Y = \exp{\left(A + B / T + C \ln(T) + D T^E\right)} \]

where \(A\) to \(E\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

E

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
class DIPPR101(DIPPRP5):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-101 equation.

    This equation implements the following temperature dependence:

    $$ Y = \exp{\left(A + B / T + C \ln(T) + D T^E\right)} $$

    where $A$ to $E$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """
    _pinfo = {'A': ('', True), 'B': ('K', True), 'C': ('', True),
              'D': ('', True), 'E': ('', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 E: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, E, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-101 equation."""
        return exp(A + B/T + C*log(T) + D*T**E)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
) -> Union[float, FloatArray]

DIPPR-101 equation.

Source code in src/polykin/properties/equations/dippr.py
190
191
192
193
194
195
196
197
198
199
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-101 equation."""
    return exp(A + B/T + C*log(T) + D*T**E)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR102 ¤

DIPPR-102 equation.

This equation implements the following temperature dependence:

\[ Y = \frac{A T^B}{ 1 + C/T + D/T^2} \]

where \(A\) to \(D\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
class DIPPR102(DIPPRP4):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-102 equation.

    This equation implements the following temperature dependence:

    $$ Y = \frac{A T^B}{ 1 + C/T + D/T^2} $$

    where $A$ to $D$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('', True), 'C': ('K', True),
              'D': ('K²', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-102 equation."""
        return (A * T**B) / (1 + C/T + D/T**2)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
) -> Union[float, FloatArray]

DIPPR-102 equation.

Source code in src/polykin/properties/equations/dippr.py
253
254
255
256
257
258
259
260
261
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-102 equation."""
    return (A * T**B) / (1 + C/T + D/T**2)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR104 ¤

DIPPR-104 equation.

This equation implements the following temperature dependence:

\[ Y = A + B/T + C/T^3 + D/T^8 + E/T^9 \]

where \(A\) to \(E\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

E

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
class DIPPR104(DIPPRP5):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-104 equation.

    This equation implements the following temperature dependence:

    $$ Y = A + B/T + C/T^3 + D/T^8 + E/T^9 $$

    where $A$ to $E$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('#·K', True), 'C': ('#·K³', True),
              'D': ('#·K⁸', True), 'E': ('#·K⁹', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 E: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, E, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-104 equation."""
        return A + B/T + C/T**3 + D/T**8 + E/T**9

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
) -> Union[float, FloatArray]

DIPPR-104 equation.

Source code in src/polykin/properties/equations/dippr.py
318
319
320
321
322
323
324
325
326
327
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-104 equation."""
    return A + B/T + C/T**3 + D/T**8 + E/T**9

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR105 ¤

DIPPR-105 equation.

This equation implements the following temperature dependence:

\[ Y = \frac{A}{B^{ \left( 1 + (1 - T / C)^D \right) }} \]

where \(A\) to \(D\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float

D

Parameter of equation.

TYPE: float

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
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
377
378
379
380
381
382
383
384
385
386
387
388
389
class DIPPR105(DIPPRP4):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-105 equation.

    This equation implements the following temperature dependence:

    $$ Y = \frac{A}{B^{ \left( 1 + (1 - T / C)^D \right) }} $$

    where $A$ to $D$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('', True), 'C': ('K', True),
              'D': ('', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-105 equation."""
        return A / B**(1 + (1 - T / C)**D)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
) -> Union[float, FloatArray]

DIPPR-105 equation.

Source code in src/polykin/properties/equations/dippr.py
381
382
383
384
385
386
387
388
389
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-105 equation."""
    return A / B**(1 + (1 - T / C)**D)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR106 ¤

DIPPR-106 equation.

This equation implements the following temperature dependence:

\[ Y = A (1 - T_r)^{B + C T_r + D T_r^2 + E T_r^3} \]

where \(A\) to \(E\) are component-specific constants, \(T\) is the absolute temperature, \(T_c\) is the critical temperature and \(T_r = T/T_c\) is the reduced temperature.

PARAMETER DESCRIPTION
Tc

Critical temperature. Unit = K.

TYPE: float

A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float DEFAULT: 0.0

D

Parameter of equation.

TYPE: float DEFAULT: 0.0

E

Parameter of equation.

TYPE: float DEFAULT: 0.0

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
class DIPPR106(DIPPR):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-106 equation.

    This equation implements the following temperature dependence:

    $$ Y = A (1 - T_r)^{B + C T_r + D T_r^2 + E T_r^3} $$

    where $A$ to $E$ are component-specific constants, $T$ is the absolute
    temperature, $T_c$ is the critical temperature and $T_r = T/T_c$ is the
    reduced temperature.

    Parameters
    ----------
    Tc : float
        Critical temperature.
        Unit = K.
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('', True), 'C': ('', True),
              'D': ('', True), 'E': ('', True), 'Tc': ('K', False)}

    def __init__(self,
                 Tc: float,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 E: float = 0.,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        self.p = {'A': A, 'B': B, 'C': C, 'D': D, 'E': E, 'Tc': Tc}
        super().__init__((Tmin, Tmax), unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float,
                 Tc: float,
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-106 equation."""
        Tr = T/Tc
        return A*(1-Tr)**(B + Tr*(C + Tr*(D + E*Tr)))

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
    Tc: float,
) -> Union[float, FloatArray]

DIPPR-106 equation.

Source code in src/polykin/properties/equations/dippr.py
452
453
454
455
456
457
458
459
460
461
462
463
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float,
             Tc: float,
             ) -> Union[float, FloatArray]:
    r"""DIPPR-106 equation."""
    Tr = T/Tc
    return A*(1-Tr)**(B + Tr*(C + Tr*(D + E*Tr)))

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

DIPPR107 ¤

DIPPR-107 equation.

This equation implements the following temperature dependence:

\[ Y = A + B\left[{\frac {C/T}{\sinh \left(C/T\right)}}\right]^2 + D\left[{\frac {E/T}{\cosh \left(E/T\right)}}\right]^2 \]

where \(A\) to \(E\) are component-specific constants and \(T\) is the absolute temperature.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation.

TYPE: float

C

Parameter of equation.

TYPE: float

D

Parameter of equation.

TYPE: float

E

Parameter of equation.

TYPE: float

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of output variable \(Y\).

TYPE: str DEFAULT: '-'

symbol

Symbol of output variable \(Y\).

TYPE: str DEFAULT: 'Y'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/dippr.py
466
467
468
469
470
471
472
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
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
class DIPPR107(DIPPRP5):
    r"""[DIPPR](https://de.wikipedia.org/wiki/DIPPR-Gleichungen)-107 equation.

    This equation implements the following temperature dependence:

    $$ Y = A + B\left[{\frac {C/T}{\sinh \left(C/T\right)}}\right]^2 +
        D\left[{\frac {E/T}{\cosh \left(E/T\right)}}\right]^2 $$

    where $A$ to $E$ are component-specific constants and $T$ is the absolute
    temperature.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
    C : float
        Parameter of equation.
    D : float
        Parameter of equation.
    E : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of output variable $Y$.
    symbol : str
        Symbol of output variable $Y$.
    name : str
        Name.
    """

    _pinfo = {'A': ('#', True), 'B': ('#', True), 'C': ('K', True),
              'D': ('#', True), 'E': ('K', True)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = '-',
                 symbol: str = 'Y',
                 name: str = ''
                 ) -> None:

        super().__init__(A, B, C, D, E, Tmin, Tmax, unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 E: float
                 ) -> Union[float, FloatArray]:
        r"""DIPPR-107 equation."""
        return A + B*(C/T/sinh(C/T))**2 + D*(E/T/cosh(E/T))**2

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    E: float,
) -> Union[float, FloatArray]

DIPPR-107 equation.

Source code in src/polykin/properties/equations/dippr.py
521
522
523
524
525
526
527
528
529
530
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             E: float
             ) -> Union[float, FloatArray]:
    r"""DIPPR-107 equation."""
    return A + B*(C/T/sinh(C/T))**2 + D*(E/T/cosh(E/T))**2

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

Wagner ¤

Wagner equation for vapor pressure.

This equation implements the following temperature dependence:

\[ \ln(P^*/P_c) = \frac{a\tau + b\tau^{1.5} + c\tau^{2.5} + d\tau^5}{T_r}\]

with:

\[ \tau = 1 - T_r\]

where \(a\) to \(d\) are component-specific constants, \(P^*\) is the vapor pressure, \(P_c\) is the critical pressure, \(T\) is the absolute temperature, \(T_c\) is the critical temperature, and \(T_r=T/T_c\) is the reduced temperature.

Note

There are several versions of the Wagner equation with different exponents. This is the so-called 25 version also used in the ThermoData Engine.

PARAMETER DESCRIPTION
Tc

Critical temperature. Unit = K.

TYPE: float

Pc

Critical pressure. Unit = Any.

TYPE: float

a

Parameter of equation.

TYPE: float

b

Parameter of equation.

TYPE: float

c

Parameter of equation.

TYPE: float

d

Parameter of equation.

TYPE: float

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of vapor pressure.

TYPE: str DEFAULT: 'Pa'

symbol

Symbol of vapor pressure.

TYPE: str DEFAULT: 'P^*'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/vapor_pressure.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
226
227
228
229
230
231
232
233
234
235
class Wagner(PropertyEquationT):
    r"""[Wagner](https://de.wikipedia.org/wiki/Wagner-Gleichung) equation for
    vapor pressure.

    This equation implements the following temperature dependence:

    $$ \ln(P^*/P_c) = \frac{a\tau + b\tau^{1.5} + c\tau^{2.5} + d\tau^5}{T_r}$$

    with:

    $$ \tau = 1 - T_r$$

    where $a$ to $d$ are component-specific constants, $P^*$ is the vapor
    pressure, $P_c$ is the critical pressure, $T$ is the absolute temperature,
    $T_c$ is the critical temperature, and $T_r=T/T_c$ is the reduced
    temperature.

    !!! note

        There are several versions of the Wagner equation with different
        exponents. This is the so-called 25 version also used in the
        [ThermoData Engine](https://trc.nist.gov/tde.html).

    Parameters
    ----------
    Tc : float
        Critical temperature.
        Unit = K.
    Pc : float
        Critical pressure.
        Unit = Any.
    a : float
        Parameter of equation.
    b : float
        Parameter of equation.
    c : float
        Parameter of equation.
    d : float
        Parameter of equation.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of vapor pressure.
    symbol : str
        Symbol of vapor pressure.
    name : str
        Name.
    """

    _pinfo = {'a': ('', True), 'b': ('', True), 'c': ('', True),
              'd': ('', True), 'Pc': ('#', False), 'Tc': ('K', False)}

    def __init__(self,
                 a: float,
                 b: float,
                 c: float,
                 d: float,
                 Pc: float,
                 Tc: float,
                 Tmin: float = 0.0,
                 Tmax: float = np.inf,
                 unit: str = 'Pa',
                 symbol: str = 'P^*',
                 name: str = ''
                 ) -> None:

        self.p = {'a': a, 'b': b, 'c': c, 'd': d, 'Pc': Pc, 'Tc': Tc}
        super().__init__((Tmin, Tmax), unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 a: float,
                 b: float,
                 c: float,
                 d: float,
                 Pc: float,
                 Tc: float,
                 ) -> Union[float, FloatArray]:
        r"""Wagner equation.

        Parameters
        ----------
        T : float | FloatArray
            Temperature. Unit = K.
        a : float
            Parameter of equation.
        b : float
            Parameter of equation.
        c : float
            Parameter of equation.
        d : float
            Parameter of equation.
        Pc : float
            Critical pressure.
            Unit = Any.
        Tc : float
            Critical temperature.
            Unit = K.

        Returns
        -------
        float | FloatArray
            Vapor pressure. Unit = [Pc].
        """
        Tr = T/Tc
        t = 1 - Tr
        return Pc*exp((a*t + b*t**1.5 + c*t**2.5 + d*t**5)/Tr)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    a: float,
    b: float,
    c: float,
    d: float,
    Pc: float,
    Tc: float,
) -> Union[float, FloatArray]

Wagner equation.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: float | FloatArray

a

Parameter of equation.

TYPE: float

b

Parameter of equation.

TYPE: float

c

Parameter of equation.

TYPE: float

d

Parameter of equation.

TYPE: float

Pc

Critical pressure. Unit = Any.

TYPE: float

Tc

Critical temperature. Unit = K.

TYPE: float

RETURNS DESCRIPTION
float | FloatArray

Vapor pressure. Unit = [Pc].

Source code in src/polykin/properties/equations/vapor_pressure.py
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
226
227
228
229
230
231
232
233
234
235
@staticmethod
def equation(T: Union[float, FloatArray],
             a: float,
             b: float,
             c: float,
             d: float,
             Pc: float,
             Tc: float,
             ) -> Union[float, FloatArray]:
    r"""Wagner equation.

    Parameters
    ----------
    T : float | FloatArray
        Temperature. Unit = K.
    a : float
        Parameter of equation.
    b : float
        Parameter of equation.
    c : float
        Parameter of equation.
    d : float
        Parameter of equation.
    Pc : float
        Critical pressure.
        Unit = Any.
    Tc : float
        Critical temperature.
        Unit = K.

    Returns
    -------
    float | FloatArray
        Vapor pressure. Unit = [Pc].
    """
    Tr = T/Tc
    t = 1 - Tr
    return Pc*exp((a*t + b*t**1.5 + c*t**2.5 + d*t**5)/Tr)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)

Yaws ¤

Yaws equation for saturated liquid viscosity.

This equation implements the following temperature dependence:

\[ \log_{base} \mu = A + \frac{B}{T} + C T + D T^2 \]

where \(A\) to \(D\) are component-specific constants, \(\mu\) is the liquid viscosity, and \(T\) is the temperature. When \(C=D=0\), this equation reverts to the Andrade equation.

PARAMETER DESCRIPTION
A

Parameter of equation.

TYPE: float

B

Parameter of equation. Unit = K.

TYPE: float

C

Parameter of equation. Unit = K⁻¹.

TYPE: float DEFAULT: 0.0

D

Parameter of equation. Unit = K⁻².

TYPE: float DEFAULT: 0.0

base10

If True base of logarithm is 10, otherwise it is \(e\).

TYPE: bool DEFAULT: True

Tmin

Lower temperature bound. Unit = K.

TYPE: float DEFAULT: 0.0

Tmax

Upper temperature bound. Unit = K.

TYPE: float DEFAULT: inf

unit

Unit of viscosity.

TYPE: str DEFAULT: 'Pa·s'

symbol

Symbol of viscosity.

TYPE: str DEFAULT: '\\mu'

name

Name.

TYPE: str DEFAULT: ''

Source code in src/polykin/properties/equations/viscosity.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 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
 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
class Yaws(PropertyEquationT):
    r"""Yaws equation for saturated liquid viscosity.

    This equation implements the following temperature dependence:

    $$ \log_{base} \mu = A + \frac{B}{T} + C T + D T^2 $$

    where $A$ to $D$ are component-specific constants, $\mu$ is the liquid
    viscosity, and $T$ is the temperature. When $C=D=0$, this equation reverts
    to the Andrade equation.

    Parameters
    ----------
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
        Unit = K.
    C : float
        Parameter of equation.
        Unit = K⁻¹.
    D : float
        Parameter of equation.
        Unit = K⁻².
    base10 : bool
        If `True` base of logarithm is `10`, otherwise it is $e$.
    Tmin : float
        Lower temperature bound.
        Unit = K.
    Tmax : float
        Upper temperature bound.
        Unit = K.
    unit : str
        Unit of viscosity.
    symbol : str
        Symbol of viscosity.
    name : str
        Name.
    """

    _pinfo = {'A': ('', True), 'B': ('K', True), 'C': ('K⁻¹', True),
              'D': ('K⁻²', True), 'base10': ('', False)}

    def __init__(self,
                 A: float,
                 B: float,
                 C: float = 0.,
                 D: float = 0.,
                 base10: bool = True,
                 Tmin: float = 0.,
                 Tmax: float = np.inf,
                 unit: str = 'Pa·s',
                 symbol: str = r'\mu',
                 name: str = ''
                 ) -> None:
        """Construct `Yaws` with the given parameters."""

        self.p = {'A': A, 'B': B, 'C': C, 'D': D, 'base10': base10}
        super().__init__((Tmin, Tmax), unit, symbol, name)

    @staticmethod
    def equation(T: Union[float, FloatArray],
                 A: float,
                 B: float,
                 C: float,
                 D: float,
                 base10: bool
                 ) -> Union[float, FloatArray]:
        r"""Yaws equation.

        Parameters
        ----------
        T : float | FloatArray
            Temperature. Unit = K.
        A : float
            Parameter of equation.
        B : float
            Parameter of equation.
            Unit = K.
        C : float
            Parameter of equation.
            Unit = K⁻¹.
        D : float
            Parameter of equation.
            Unit = K⁻².
        base10 : bool
            If `True` base of logarithm is `10`, otherwise it is $e$.

        Returns
        -------
        float | FloatArray
            Viscosity. Unit = Any.
        """
        x = A + B/T + C*T + D*T**2
        if base10:
            return 10**x
        else:
            return exp(x)

__call__ ¤

__call__(
    T: Union[float, FloatArrayLike],
    Tunit: Literal["C", "K"] = "K",
) -> Union[float, FloatArray]

Evaluate property equation at given temperature, including unit conversion and range check.

PARAMETER DESCRIPTION
T

Temperature. Unit = Tunit.

TYPE: float | FloatArrayLike

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

RETURNS DESCRIPTION
float | FloatArray

Correlation value.

Source code in src/polykin/properties/equations/base.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __call__(self,
             T: Union[float, FloatArrayLike],
             Tunit: Literal['C', 'K'] = 'K'
             ) -> Union[float, FloatArray]:
    r"""Evaluate property equation at given temperature, including unit
    conversion and range check.

    Parameters
    ----------
    T : float | FloatArrayLike
        Temperature.
        Unit = `Tunit`.
    Tunit : Literal['C', 'K']
        Temperature unit.

    Returns
    -------
    float | FloatArray
        Correlation value.
    """
    TK = convert_check_temperature(T, Tunit, self.Trange)
    return self.equation(TK, **self.p)

__init__ ¤

__init__(
    A: float,
    B: float,
    C: float = 0.0,
    D: float = 0.0,
    base10: bool = True,
    Tmin: float = 0.0,
    Tmax: float = np.inf,
    unit: str = "Pa·s",
    symbol: str = "\\mu",
    name: str = "",
) -> None

Construct Yaws with the given parameters.

Source code in src/polykin/properties/equations/viscosity.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def __init__(self,
             A: float,
             B: float,
             C: float = 0.,
             D: float = 0.,
             base10: bool = True,
             Tmin: float = 0.,
             Tmax: float = np.inf,
             unit: str = 'Pa·s',
             symbol: str = r'\mu',
             name: str = ''
             ) -> None:
    """Construct `Yaws` with the given parameters."""

    self.p = {'A': A, 'B': B, 'C': C, 'D': D, 'base10': base10}
    super().__init__((Tmin, Tmax), unit, symbol, name)

equation staticmethod ¤

equation(
    T: Union[float, FloatArray],
    A: float,
    B: float,
    C: float,
    D: float,
    base10: bool,
) -> Union[float, FloatArray]

Yaws equation.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: float | FloatArray

A

Parameter of equation.

TYPE: float

B

Parameter of equation. Unit = K.

TYPE: float

C

Parameter of equation. Unit = K⁻¹.

TYPE: float

D

Parameter of equation. Unit = K⁻².

TYPE: float

base10

If True base of logarithm is 10, otherwise it is \(e\).

TYPE: bool

RETURNS DESCRIPTION
float | FloatArray

Viscosity. Unit = Any.

Source code in src/polykin/properties/equations/viscosity.py
 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
@staticmethod
def equation(T: Union[float, FloatArray],
             A: float,
             B: float,
             C: float,
             D: float,
             base10: bool
             ) -> Union[float, FloatArray]:
    r"""Yaws equation.

    Parameters
    ----------
    T : float | FloatArray
        Temperature. Unit = K.
    A : float
        Parameter of equation.
    B : float
        Parameter of equation.
        Unit = K.
    C : float
        Parameter of equation.
        Unit = K⁻¹.
    D : float
        Parameter of equation.
        Unit = K⁻².
    base10 : bool
        If `True` base of logarithm is `10`, otherwise it is $e$.

    Returns
    -------
    float | FloatArray
        Viscosity. Unit = Any.
    """
    x = A + B/T + C*T + D*T**2
    if base10:
        return 10**x
    else:
        return exp(x)

fit ¤

fit(
    T: FloatVectorLike,
    Y: FloatVectorLike,
    sigmaY: Optional[FloatVectorLike] = None,
    fit_only: Optional[list[str]] = None,
    logY: bool = False,
    plot: bool = True,
) -> dict

Fit equation to data using non-linear regression.

PARAMETER DESCRIPTION
T

Temperature. Unit = K.

TYPE: FloatVector

Y

Property to be fitted. Unit = Any.

TYPE: FloatVector

sigmaY

Standard deviation of Y. Unit = [Y].

TYPE: FloatVector | None DEFAULT: None

fit_only

List with name of parameters to be fitted.

TYPE: list[str] | None DEFAULT: None

logY

If True, the fit will be done in terms of log(Y).

TYPE: bool DEFAULT: False

plot

If True a plot comparing data and fitted correlation will be generated.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict

A dictionary of results with the following keys: 'success', 'parameters', 'covariance', and 'plot'.

Source code in src/polykin/properties/equations/base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def fit(self,
        T: FloatVectorLike,
        Y: FloatVectorLike,
        sigmaY: Optional[FloatVectorLike] = None,
        fit_only: Optional[list[str]] = None,
        logY: bool = False,
        plot: bool = True,
        ) -> dict:
    """Fit equation to data using non-linear regression.

    Parameters
    ----------
    T : FloatVector
        Temperature. Unit = K.
    Y : FloatVector
        Property to be fitted. Unit = Any.
    sigmaY : FloatVector | None
        Standard deviation of Y. Unit = [Y].
    fit_only : list[str] | None
        List with name of parameters to be fitted.
    logY : bool
        If `True`, the fit will be done in terms of log(Y).
    plot : bool
        If `True` a plot comparing data and fitted correlation will be
        generated.

    Returns
    -------
    dict
        A dictionary of results with the following keys: 'success',
        'parameters', 'covariance', and 'plot'.
    """

    # Current parameter values
    pdict = self.p.copy()

    # Select parameters to be fitted
    pnames_fit = [name for name, info in self._pinfo.items() if info[1]]
    if fit_only:
        pnames_fit = set(fit_only) & set(pnames_fit)
    p0 = [pdict[pname] for pname in pnames_fit]

    # Fit function
    def ffit(x, *p):
        for pname, pvalue in zip(pnames_fit, p):
            pdict[pname] = pvalue
        Yfit = self.equation(T=x, **pdict)
        if logY:
            Yfit = log(Yfit)
        return Yfit

    solution = curve_fit(ffit,
                         xdata=T,
                         ydata=log(Y) if logY else Y,
                         p0=p0,
                         sigma=sigmaY,
                         absolute_sigma=False,
                         full_output=True)
    result = {}
    result['success'] = bool(solution[4])
    if solution[4]:
        popt = solution[0]
        pcov = solution[1]
        print("Fit successful.")
        for pname, pvalue in zip(pnames_fit, popt):
            print(f"{pname}: {pvalue}")
        print("Covariance:")
        print(pcov)
        result['covariance'] = pcov

        # Update attributes
        self.Trange = (min(T), max(T))
        for pname, pvalue in zip(pnames_fit, popt):
            self.p[pname] = pvalue
        result['parameters'] = pdict

        # plot
        if plot:
            kind = 'semilogy' if logY else 'linear'
            fig, ax = self.plot(kind=kind, return_objects=True)  # ok
            ax.plot(T, Y, 'o', mfc='none')
            result['plot'] = (fig, ax)
    else:
        print("Fit error: ", solution[3])
        result['message'] = solution[3]

    return result

plot ¤

plot(
    kind: Literal[
        "linear", "semilogy", "Arrhenius"
    ] = "linear",
    Trange: Optional[tuple[float, float]] = None,
    Tunit: Literal["C", "K"] = "K",
    title: Optional[str] = None,
    axes: Optional[Axes] = None,
    return_objects: bool = False,
) -> Optional[tuple[Optional[Figure], Axes]]

Plot quantity as a function of temperature.

PARAMETER DESCRIPTION
kind

Kind of plot to be generated.

TYPE: Literal['linear', 'semilogy', 'Arrhenius'] DEFAULT: 'linear'

Trange

Temperature range for x-axis. If None, the validity range (Tmin, Tmax) will be used. If no validity range was defined, the range will default to 0-100°C.

TYPE: tuple[float, float] | None DEFAULT: None

Tunit

Temperature unit.

TYPE: Literal['C', 'K'] DEFAULT: 'K'

title

Title of plot. If None, the object name will be used.

TYPE: str | None DEFAULT: None

axes

Matplotlib Axes object.

TYPE: Axes | None DEFAULT: None

return_objects

If True, the Figure and Axes objects are returned (for saving or further manipulations).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
tuple[Figure | None, Axes] | None

Figure and Axes objects if return_objects is True.

Source code in src/polykin/properties/equations/base.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
def plot(self,
         kind: Literal['linear', 'semilogy', 'Arrhenius'] = 'linear',
         Trange: Optional[tuple[float, float]] = None,
         Tunit: Literal['C', 'K'] = 'K',
         title: Optional[str] = None,
         axes: Optional[Axes] = None,
         return_objects: bool = False
         ) -> Optional[tuple[Optional[Figure], Axes]]:
    """Plot quantity as a function of temperature.

    Parameters
    ----------
    kind : Literal['linear', 'semilogy', 'Arrhenius']
        Kind of plot to be generated.
    Trange : tuple[float, float] | None
        Temperature range for x-axis. If `None`, the validity range
        (Tmin, Tmax) will be used. If no validity range was defined, the
        range will default to 0-100°C.
    Tunit : Literal['C', 'K']
        Temperature unit.
    title : str | None
        Title of plot. If `None`, the object name will be used.
    axes : Axes | None
        Matplotlib Axes object.
    return_objects : bool
        If `True`, the Figure and Axes objects are returned (for saving or
        further manipulations).

    Returns
    -------
    tuple[Figure | None, Axes] | None
        Figure and Axes objects if return_objects is `True`.
    """

    # Check inputs
    check_in_set(kind, {'linear', 'semilogy', 'Arrhenius'}, 'kind')
    check_in_set(Tunit, {'K', 'C'}, 'Tunit')
    if Trange is not None:
        Trange_min = 0.
        if Tunit == 'C':
            Trange_min = -273.15
        check_valid_range(Trange, Trange_min, np.inf, 'Trange')

    # Plot objects
    if axes is None:
        fig, ax = plt.subplots()
        if title is None:
            title = self.name
        if title:
            fig.suptitle(title)
        label = None
    else:
        fig = None
        ax = axes
        label = self.name

    # Units and xlabel
    Tunit_range = Tunit
    if kind == 'Arrhenius':
        Tunit = 'K'
    Tsymbol = Tunit
    if Tunit == 'C':
        Tsymbol = '°' + Tunit

    if kind == 'Arrhenius':
        xlabel = r"$1/T$ [" + Tsymbol + r"$^{-1}$]"
    else:
        xlabel = fr"$T$ [{Tsymbol}]"

    # ylabel
    ylabel = fr"${self.symbol}$ [{self.unit}]"
    if axes is not None:
        ylabel0 = ax.get_ylabel()
        if ylabel0 and ylabel not in ylabel0:
            ylabel = ylabel0 + ", " + ylabel

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True)

    # x-axis vector
    if Trange is not None:
        if Tunit_range == 'C':
            Trange = (Trange[0]+273.15, Trange[1]+273.15)
    else:
        Trange = (np.min(self.Trange[0]), np.max(self.Trange[1]))
        if Trange == (0.0, np.inf):
            Trange = (273.15, 373.15)

    try:
        shape = self._shape
    except AttributeError:
        shape = None
    if shape is not None:
        print("Plot method not yet implemented for array-like equations.")
    else:
        TK = np.linspace(*Trange, 100)
        y = self.__call__(TK, 'K')
        T = TK
        if Tunit == 'C':
            T -= 273.15
        if kind == 'linear':
            ax.plot(T, y, label=label)
        elif kind == 'semilogy':
            ax.semilogy(T, y, label=label)
        elif kind == 'Arrhenius':
            ax.semilogy(1/TK, y, label=label)

    if fig is None:
        ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")

    if return_objects:
        return (fig, ax)