Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Added SAFT-VRQ Mie equation of state and Helmholtz energy functional for first order Feynman-Hibbs corrected Mie fluids. [#79](https://github.com/feos-org/feos/pull/79)
- Added `estimator` module to documentation. [#86](https://github.com/feos-org/feos/pull/86)

### Changed
- Export `EosVariant` and `FunctionalVariant` directly in the crate root instead of their own modules. [#62](https://github.com/feos-org/feos/pull/62)
- Changed constructors `VaporPressure::new` and `DataSet.vapor_pressure` (Python) to take a new optional argument `critical_temperature`. [#86](https://github.com/feos-org/feos/pull/86)

## [0.3.0] - 2022-09-14
- Major restructuring of the entire `feos` project. All individual models are reunited in the `feos` crate. `feos-core` and `feos-dft` still live as individual crates within the `feos` workspace.
Expand Down
24 changes: 23 additions & 1 deletion docs/api/eos.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
The `eos` module contains the `EquationOfState` object that contains all implemented equations of state.
The `State` and `PhaseEquilibrium` objects are used to define thermodynamic conditions and -- once created -- can be used to compute properties.

If you want to adjust parameters of a model to experimental data you can use classes and utilities from the `estimator` module.

## `EquationOfState`

```{eval-rst}
Expand Down Expand Up @@ -34,4 +36,24 @@ The `State` and `PhaseEquilibrium` objects are used to define thermodynamic cond
State
PhaseEquilibrium
PhaseDiagram
```
```

## The `estimator` module

### Import

```python
from feos.eos.estimator import Estimator, DataSet, Loss, Phase
```

```{eval-rst}
.. currentmodule:: feos.eos.estimator

.. autosummary::
:toctree: generated/

Estimator
DataSet
Loss
Phase
```
71 changes: 44 additions & 27 deletions src/estimator/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ impl From<EstimatorError> for PyErr {
#[macro_export]
macro_rules! impl_estimator {
($eos:ty, $py_eos:ty) => {
/// Loss function that is applied to the residuals to
/// weight to in- and outliers.
Comment thread
g-bauer marked this conversation as resolved.
Outdated
#[pyclass(name = "Loss")]
#[derive(Clone)]
pub struct PyLoss(Loss);
Expand Down Expand Up @@ -121,20 +123,26 @@ macro_rules! impl_estimator {
impl PyDataSet {
/// Compute the cost function for each input value.
///
/// The cost function that is used depends on the
/// property. See the class constructors to learn
/// about the cost functions of the properties.
///
/// Parameters
/// ----------
/// eos : PyEos
/// eos : EquationOfState
/// The equation of state that is used.
/// loss : Loss
/// The loss function that is applied to residuals
/// to distinguish between in- and outliers.
///
/// Returns
/// -------
/// numpy.ndarray[Float]
/// The cost function evaluated for each experimental data point.
#[pyo3(text_signature = "($self, eos)")]
///
/// Note
/// ----
/// The cost function that is used depends on the
/// property. For most properties it is the absolute relative difference.
/// See the constructors of the respective properties
/// to learn about the cost functions that are used.
#[pyo3(text_signature = "($self, eos, loss)")]
fn cost<'py>(
&self,
eos: &$py_eos,
Expand All @@ -149,18 +157,12 @@ macro_rules! impl_estimator {
///
/// Parameters
/// ----------
/// eos : PyEos
/// eos : EquationOfState
/// The equation of state that is used.
///
/// Returns
/// -------
/// SIArray1
///
/// See also
/// --------
/// eos_python.saft.estimator.DataSet.vapor_pressure : ``DataSet`` for vapor pressure.
/// eos_python.saft.estimator.DataSet.liquid_density : ``DataSet`` for liquid density.
/// eos_python.saft.estimator.DataSet.equilibrium_liquid_density : ``DataSet`` for liquid density at vapor liquid equilibrium.
#[pyo3(text_signature = "($self, eos)")]
fn predict(&self, eos: &$py_eos) -> PyResult<PySIArray1> {
Ok(self.0.predict(&eos.0)?.into())
Expand All @@ -175,7 +177,7 @@ macro_rules! impl_estimator {
///
/// Parameters
/// ----------
/// eos : PyEos
/// eos : EquationOfState
/// The equation of state that is used.
///
/// Returns
Expand All @@ -198,7 +200,7 @@ macro_rules! impl_estimator {
///
/// Parameters
/// ----------
/// eos : PyEos
/// eos : EquationOfState
/// The equation of state that is used.
///
/// Returns
Expand All @@ -221,6 +223,10 @@ macro_rules! impl_estimator {
/// Use Antoine type equation to extrapolate vapor
/// pressure if experimental data is above critial
/// point of model. Defaults to False.
/// critical_temperature : SINumber, optional
/// Estimate of the critical temperature used as initial
/// value for critical point calculation. Defaults to None.
/// For additional information, see note.
/// max_iter : int, optional
/// The maximum number of iterations for critical point
/// and VLE algorithms.
Expand All @@ -234,12 +240,19 @@ macro_rules! impl_estimator {
/// Returns
/// -------
/// ``DataSet``
///
/// Note
/// ----
/// If no critical temperature is provided, the maximum of the `temperature` input
/// is used. If that fails, the default temperatures of the critical point routine
/// are used.
#[staticmethod]
#[pyo3(text_signature = "(target, temperature, extrapolate)")]
#[pyo3(text_signature = "(target, temperature, extrapolate, critical_temperature=None, max_iter=None, verbosity=None)")]
fn vapor_pressure(
target: &PySIArray1,
temperature: &PySIArray1,
extrapolate: Option<bool>,
critical_temperature: Option<&PySINumber>,
max_iter: Option<usize>,
tol: Option<f64>,
verbosity: Option<Verbosity>,
Expand All @@ -248,6 +261,7 @@ macro_rules! impl_estimator {
target.clone().into(),
temperature.clone().into(),
extrapolate.unwrap_or(false),
critical_temperature.and_then(|tc| Some(tc.clone().into())),
Some((max_iter, tol, verbosity).into()),
)?)))
}
Expand Down Expand Up @@ -439,7 +453,7 @@ macro_rules! impl_estimator {
PySIArray1::from(self.0.target().clone())
}

/// Return `target` as ``SIArray1``.
/// Return number of stored data points.
#[getter]
fn get_datapoints(&self) -> usize {
self.0.datapoints()
Expand Down Expand Up @@ -483,22 +497,25 @@ macro_rules! impl_estimator {

/// Compute the cost function for each ``DataSet``.
///
/// The cost function is:
/// - The relative difference between prediction and target value,
/// - to which a loss function is applied,
/// - and which is weighted according to the number of datapoints,
/// - and the relative weights as defined in the Estimator object.
///
/// Parameters
/// ----------
/// eos : PyEos
/// eos : EquationOfState
/// The equation of state that is used.
///
/// Returns
/// -------
/// numpy.ndarray[Float]
/// The cost function evaluated for each experimental data point
/// of each ``DataSet``.
///
/// Note
/// ----
/// The cost function is:
///
/// - The relative difference between prediction and target value,
/// - to which a loss function is applied,
/// - and which is weighted according to the number of datapoints,
/// - and the relative weights as defined in the Estimator object.
#[pyo3(text_signature = "($self, eos)")]
fn cost<'py>(&self, eos: &$py_eos, py: Python<'py>) -> PyResult<&'py PyArray1<f64>> {
Ok(self.0.cost(&eos.0)?.view().to_pyarray(py))
Expand All @@ -509,7 +526,7 @@ macro_rules! impl_estimator {
///
/// Parameters
/// ----------
/// eos : PyEos
/// eos : EquationOfState
/// The equation of state that is used.
///
/// Returns
Expand All @@ -534,7 +551,7 @@ macro_rules! impl_estimator {
///
/// Parameters
/// ----------
/// eos : PyEos
/// eos : EquationOfState
/// The equation of state that is used.
///
/// Returns
Expand Down Expand Up @@ -562,7 +579,7 @@ macro_rules! impl_estimator {
///
/// Parameters
/// ----------
/// eos : PyEos
/// eos : EquationOfState
/// The equation of state that is used.
///
/// Returns
Expand Down
18 changes: 11 additions & 7 deletions src/estimator/vapor_pressure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ impl<U: EosUnit> VaporPressure<U> {
target: QuantityArray1<U>,
temperature: QuantityArray1<U>,
extrapolate: bool,
critical_temperature: Option<QuantityScalar<U>>,
solver_options: Option<SolverOptions>,
) -> Result<Self, EstimatorError> {
let datapoints = target.len();
let max_temperature = temperature
.to_reduced(U::reference_temperature())?
.into_iter()
.reduce(|a, b| a.max(b))
.unwrap()
* U::reference_temperature();
let max_temperature = critical_temperature.unwrap_or(
temperature
.to_reduced(U::reference_temperature())?
.into_iter()
.reduce(|a, b| a.max(b))
.unwrap()
* U::reference_temperature(),
);
Ok(Self {
target,
temperature,
Expand Down Expand Up @@ -72,7 +75,8 @@ impl<U: EosUnit, E: EquationOfState> DataSet<U, E> for VaporPressure<U> {
QuantityScalar<U>: std::fmt::Display + std::fmt::LowerExp,
{
let critical_point =
State::critical_point(eos, None, Some(self.max_temperature), self.solver_options)?;
State::critical_point(eos, None, Some(self.max_temperature), self.solver_options)
.or_else(|_| State::critical_point(eos, None, None, self.solver_options))?;
let tc = critical_point.temperature;
let pc = critical_point.pressure(Contributions::Total);

Expand Down