From 2332c3418d55663df21bebeacc269f135594750f Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Sun, 14 Jun 2026 18:32:40 +0200 Subject: [PATCH 1/3] Update PyO3 to v0.29 --- Cargo.toml | 8 ++++---- crates/feos-dft/src/adsorption/fea_potential.rs | 11 ++++++++--- py-feos/Cargo.toml | 6 +++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eb351252c..40b1008b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,10 +22,10 @@ keywords = [ categories = ["science"] [workspace.dependencies] -quantity = "0.13" -num-dual = "0.13" +quantity = "0.14" +num-dual = "0.14" ndarray = "0.17" -nalgebra = "0.34" +nalgebra = "0.35" thiserror = "2.0" conv = "0.3" num-traits = "0.2" @@ -38,7 +38,7 @@ petgraph = "0.8" rustdct = "0.7" rustfft = "6.0" libm = "0.2" -gauss-quad = "0.2" +gauss-quad = "0.3" approx = "0.5" criterion = "0.8" paste = "1.0" diff --git a/crates/feos-dft/src/adsorption/fea_potential.rs b/crates/feos-dft/src/adsorption/fea_potential.rs index ef4b76964..d05ed6a01 100644 --- a/crates/feos-dft/src/adsorption/fea_potential.rs +++ b/crates/feos-dft/src/adsorption/fea_potential.rs @@ -1,11 +1,12 @@ use super::pore3d::{calculate_distance2, evaluate_lj_potential}; -use crate::profile::{CUTOFF_RADIUS, MAX_POTENTIAL}; use crate::Geometry; +use crate::profile::{CUTOFF_RADIUS, MAX_POTENTIAL}; use feos_core::ReferenceSystem; use gauss_quad::GaussLegendre; use ndarray::{Array1, Array2, Zip}; use quantity::Length; use std::f64::consts::PI; +use std::num::NonZero; // Calculate free-energy average potential for given solid structure. #[expect(clippy::too_many_arguments)] @@ -55,7 +56,9 @@ pub fn calculate_fea_potential( } Geometry::Spherical | Geometry::Cylindrical => { let (unscaled_nodes, unscaled_weights) = - GaussLegendre::new(n_grid[0]).unwrap().into_iter().unzip(); + GaussLegendre::new(NonZero::new(n_grid[0]).unwrap()) + .into_iter() + .unzip(); let nodes = PI + Array1::from_vec(unscaled_nodes) * PI; let weights = Array1::from_vec(unscaled_weights) * PI; @@ -80,7 +83,9 @@ pub fn calculate_fea_potential( } Geometry::Spherical => { let (unscaled_nodes, unscaled_weights) = - GaussLegendre::new(n_grid[1]).unwrap().into_iter().unzip(); + GaussLegendre::new(NonZero::new(n_grid[1]).unwrap()) + .into_iter() + .unzip(); let nodes = PI / 2.0 + Array1::from_vec(unscaled_nodes) * PI / 2.0; let weights = Array1::from_vec(unscaled_weights) * PI / 2.0 diff --git a/py-feos/Cargo.toml b/py-feos/Cargo.toml index c06369b72..08d7d74fe 100644 --- a/py-feos/Cargo.toml +++ b/py-feos/Cargo.toml @@ -15,12 +15,12 @@ name = "feos" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.27", features = [ +pyo3 = { version = "0.29", features = [ "multiple-pymethods", "indexmap" ] } -pythonize = "0.27" -numpy = { version = "0.27" } +pythonize = "0.29" +numpy = { version = "0.29" } quantity = { workspace = true, features = ["python", "python_numpy"] } num-dual = { workspace = true, features = ["python_macro"] } From 7a5f244009988602c15413c199e16ed40b735a31 Mon Sep 17 00:00:00 2001 From: Gernot Bauer Date: Mon, 15 Jun 2026 13:13:14 +0200 Subject: [PATCH 2/3] Added from_py_object --- py-feos/src/ad/mod.rs | 2 +- py-feos/src/dft/adsorption/external_potential.rs | 4 ++-- py-feos/src/dft/mod.rs | 8 ++++---- py-feos/src/dft/solver.rs | 4 ++-- py-feos/src/lib.rs | 2 +- py-feos/src/parameter/chemical_record.rs | 4 ++-- py-feos/src/parameter/fragmentation.rs | 2 +- py-feos/src/parameter/identifier.rs | 4 ++-- py-feos/src/parameter/mod.rs | 4 ++-- py-feos/src/parameter/model_record.rs | 4 ++-- py-feos/src/parameter/segment.rs | 4 ++-- py-feos/src/phase_equilibria.rs | 4 ++-- py-feos/src/state.rs | 4 ++-- py-feos/src/user_defined.rs | 4 ++-- 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/py-feos/src/ad/mod.rs b/py-feos/src/ad/mod.rs index 71eac67a0..12a6c6345 100644 --- a/py-feos/src/ad/mod.rs +++ b/py-feos/src/ad/mod.rs @@ -14,7 +14,7 @@ use pyo3::prelude::*; pub mod dataset; pub use dataset::{PyBinaryDataset, PyPureDataset}; -#[pyclass(name = "EquationOfStateAD", eq, eq_int)] +#[pyclass(name = "EquationOfStateAD", eq, eq_int, from_py_object)] #[derive(Clone, Copy, PartialEq)] pub enum PyEquationOfStateAD { PcSaftNonAssoc, diff --git a/py-feos/src/dft/adsorption/external_potential.rs b/py-feos/src/dft/adsorption/external_potential.rs index bf4d6e209..290fe6c03 100644 --- a/py-feos/src/dft/adsorption/external_potential.rs +++ b/py-feos/src/dft/adsorption/external_potential.rs @@ -1,12 +1,12 @@ use feos_dft::adsorption::ExternalPotential; use ndarray::Array2; -use numpy::prelude::*; use numpy::PyArray1; +use numpy::prelude::*; use pyo3::prelude::*; use quantity::Length; /// A collection of external potentials. -#[pyclass(name = "ExternalPotential")] +#[pyclass(name = "ExternalPotential", from_py_object)] #[derive(Clone)] pub struct PyExternalPotential(pub ExternalPotential); diff --git a/py-feos/src/dft/mod.rs b/py-feos/src/dft/mod.rs index 5986ee0d0..2ff6c4f34 100644 --- a/py-feos/src/dft/mod.rs +++ b/py-feos/src/dft/mod.rs @@ -1,4 +1,4 @@ -use crate::eos::{parse_molefracs, PyEquationOfState}; +use crate::eos::{PyEquationOfState, parse_molefracs}; use crate::ideal_gas::IdealGasModel; use crate::residual::ResidualModel; use feos::hard_sphere::{FMTFunctional, FMTVersion}; @@ -24,7 +24,7 @@ pub(crate) use solver::{PyDFTSolver, PyDFTSolverLog}; /// Geometries of individual axes. #[derive(Clone, Copy, PartialEq)] -#[pyclass(name = "Geometry", eq, eq_int)] +#[pyclass(name = "Geometry", eq, eq_int, from_py_object)] pub enum PyGeometry { Cartesian, Cylindrical, @@ -53,7 +53,7 @@ impl From for Geometry { /// Different versions of fundamental measure theory. #[derive(Clone, Copy, PartialEq)] -#[pyclass(name = "FMTVersion", eq, eq_int)] +#[pyclass(name = "FMTVersion", eq, eq_int, from_py_object)] pub enum PyFMTVersion { /// White Bear ([Roth et al., 2002](https://doi.org/10.1088/0953-8984/14/46/313)) or modified ([Yu and Wu, 2002](https://doi.org/10.1063/1.1520530)) fundamental measure theory WhiteBear, @@ -84,7 +84,7 @@ impl From for FMTVersion { } /// Collection of Helmholtz energy functionals. -#[pyclass(name = "HelmholtzEnergyFunctional")] +#[pyclass(name = "HelmholtzEnergyFunctional", from_py_object)] #[derive(Clone)] pub struct PyHelmholtzEnergyFunctional; diff --git a/py-feos/src/dft/solver.rs b/py-feos/src/dft/solver.rs index 2c23d57c3..4ee5e7410 100644 --- a/py-feos/src/dft/solver.rs +++ b/py-feos/src/dft/solver.rs @@ -16,7 +16,7 @@ use quantity::Time; /// Returns /// ------- /// DFTSolver -#[pyclass(name = "DFTSolver")] +#[pyclass(name = "DFTSolver", from_py_object)] #[derive(Clone)] pub struct PyDFTSolver(pub DFTSolver); @@ -160,7 +160,7 @@ impl PyDFTSolver { } } -#[pyclass(name = "DFTSolverLog")] +#[pyclass(name = "DFTSolverLog", from_py_object)] #[derive(Clone)] pub struct PyDFTSolverLog(pub DFTSolverLog); diff --git a/py-feos/src/lib.rs b/py-feos/src/lib.rs index 017dbb4a6..bd191ed12 100644 --- a/py-feos/src/lib.rs +++ b/py-feos/src/lib.rs @@ -19,7 +19,7 @@ pub(crate) mod user_defined; /// Output level for phase equilibrium solvers. #[derive(Debug, Clone, Copy, PartialEq)] -#[pyclass(name = "Verbosity", eq, eq_int)] +#[pyclass(name = "Verbosity", eq, eq_int, from_py_object)] pub(crate) enum PyVerbosity { /// Do not print output. None, diff --git a/py-feos/src/parameter/chemical_record.rs b/py-feos/src/parameter/chemical_record.rs index b086a0f0f..c3a2d35b9 100644 --- a/py-feos/src/parameter/chemical_record.rs +++ b/py-feos/src/parameter/chemical_record.rs @@ -1,4 +1,4 @@ -use super::fragmentation::{fragment_molecule, PySmartsRecord}; +use super::fragmentation::{PySmartsRecord, fragment_molecule}; use super::identifier::{PyIdentifier, PyIdentifierOption}; use crate::error::PyFeosError; use feos_core::parameter::{ChemicalRecord, Identifier}; @@ -7,7 +7,7 @@ use pyo3::prelude::*; use serde::{Deserialize, Serialize}; /// Information about segments and bonds of a molecule. -#[pyclass(name = "ChemicalRecord")] +#[pyclass(name = "ChemicalRecord", from_py_object)] #[derive(Deserialize, Serialize, Debug, Clone)] pub(crate) struct PyChemicalRecord(ChemicalRecord); diff --git a/py-feos/src/parameter/fragmentation.rs b/py-feos/src/parameter/fragmentation.rs index b011f9fc5..8d9fb9642 100644 --- a/py-feos/src/parameter/fragmentation.rs +++ b/py-feos/src/parameter/fragmentation.rs @@ -9,7 +9,7 @@ use crate::error::PyFeosError; /// SMARTS code, required to fragmentize molecules into segments. #[derive(Clone, Serialize, Deserialize)] -#[pyclass(name = "SmartsRecord")] +#[pyclass(name = "SmartsRecord", from_py_object)] pub(crate) struct PySmartsRecord { group: String, smarts: String, diff --git a/py-feos/src/parameter/identifier.rs b/py-feos/src/parameter/identifier.rs index 4baf195db..e8057c077 100644 --- a/py-feos/src/parameter/identifier.rs +++ b/py-feos/src/parameter/identifier.rs @@ -3,7 +3,7 @@ use pyo3::prelude::*; use serde::{Deserialize, Serialize}; /// Identifier to match on while reading parameters from files. -#[pyclass(name = "IdentifierOption", eq, eq_int)] +#[pyclass(name = "IdentifierOption", eq, eq_int, from_py_object)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] pub enum PyIdentifierOption { Cas, @@ -43,7 +43,7 @@ impl From for IdentifierOption { } /// Different common identifiers for chemicals. -#[pyclass(name = "Identifier")] +#[pyclass(name = "Identifier", from_py_object)] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PyIdentifier(pub Identifier); diff --git a/py-feos/src/parameter/mod.rs b/py-feos/src/parameter/mod.rs index aabbed5ab..93586835e 100644 --- a/py-feos/src/parameter/mod.rs +++ b/py-feos/src/parameter/mod.rs @@ -22,7 +22,7 @@ pub(crate) use model_record::{PyBinaryRecord, PyPureRecord}; pub(crate) use segment::{PyBinarySegmentRecord, PySegmentRecord}; /// Set of parameters that fully characterizes a mixture. -#[pyclass(name = "Parameters")] +#[pyclass(name = "Parameters", from_py_object)] #[derive(Clone, Serialize, Deserialize)] pub struct PyParameters { pub pure_records: Vec>, @@ -521,7 +521,7 @@ impl PyParameters { /// Combination of chemical information and segment parameters that is used to /// parametrize a group-contribution model. -#[pyclass(name = "GcParameters")] +#[pyclass(name = "GcParameters", from_py_object)] #[derive(Clone, Serialize, Deserialize)] pub struct PyGcParameters { chemical_records: Vec, diff --git a/py-feos/src/parameter/model_record.rs b/py-feos/src/parameter/model_record.rs index eeb06aa8e..93e0fca54 100644 --- a/py-feos/src/parameter/model_record.rs +++ b/py-feos/src/parameter/model_record.rs @@ -15,7 +15,7 @@ use serde_json::Value; #[derive(Serialize, Deserialize, Clone)] #[serde(from = "PureRecord")] #[serde(into = "PureRecord")] -#[pyclass(name = "PureRecord")] +#[pyclass(name = "PureRecord", from_py_object)] pub struct PyPureRecord { #[pyo3(get)] pub identifier: PyIdentifier, @@ -128,7 +128,7 @@ impl PyPureRecord { #[derive(Serialize, Deserialize, Clone)] #[serde(from = "BinaryRecord")] #[serde(into = "BinaryRecord")] -#[pyclass(name = "BinaryRecord")] +#[pyclass(name = "BinaryRecord", from_py_object)] pub struct PyBinaryRecord { #[pyo3(get)] pub id1: PyIdentifier, diff --git a/py-feos/src/parameter/segment.rs b/py-feos/src/parameter/segment.rs index 1816643ec..0bf2fd3f8 100644 --- a/py-feos/src/parameter/segment.rs +++ b/py-feos/src/parameter/segment.rs @@ -10,7 +10,7 @@ use serde_json::Value; #[derive(Serialize, Deserialize, Clone)] #[serde(from = "SegmentRecord")] #[serde(into = "SegmentRecord")] -#[pyclass(name = "SegmentRecord")] +#[pyclass(name = "SegmentRecord", from_py_object)] pub struct PySegmentRecord { #[pyo3(get)] identifier: String, @@ -113,7 +113,7 @@ impl PySegmentRecord { #[derive(Serialize, Deserialize, Clone)] #[serde(from = "BinaryRecord")] #[serde(into = "BinaryRecord")] -#[pyclass(name = "BinarySegmentRecord")] +#[pyclass(name = "BinarySegmentRecord", from_py_object)] pub struct PyBinarySegmentRecord { #[pyo3(get)] pub id1: String, diff --git a/py-feos/src/phase_equilibria.rs b/py-feos/src/phase_equilibria.rs index feaa38930..17b85b91d 100644 --- a/py-feos/src/phase_equilibria.rs +++ b/py-feos/src/phase_equilibria.rs @@ -19,7 +19,7 @@ use std::ops::Deref; use std::sync::Arc; /// A thermodynamic two phase equilibrium state. -#[pyclass(name = "PhaseEquilibrium")] +#[pyclass(name = "PhaseEquilibrium", from_py_object)] #[derive(Clone)] pub struct PyPhaseEquilibrium( pub PhaseEquilibrium, ResidualModel>>, 2>, @@ -582,7 +582,7 @@ impl PyPhaseEquilibrium { } /// A thermodynamic three phase equilibrium state. -#[pyclass(name = "ThreePhaseEquilibrium")] +#[pyclass(name = "ThreePhaseEquilibrium", from_py_object)] #[derive(Clone)] struct PyThreePhaseEquilibrium( PhaseEquilibrium, ResidualModel>>, 3>, diff --git a/py-feos/src/state.rs b/py-feos/src/state.rs index 761055a3b..bb94686e3 100644 --- a/py-feos/src/state.rs +++ b/py-feos/src/state.rs @@ -23,7 +23,7 @@ type InvP = Quantity::Output>; /// Possible contributions that can be computed. #[derive(Clone, Copy, PartialEq)] -#[pyclass(name = "Contributions", eq, eq_int)] +#[pyclass(name = "Contributions", eq, eq_int, from_py_object)] pub enum PyContributions { /// Only compute the ideal gas contribution IdealGas, @@ -94,7 +94,7 @@ impl From for Contributions { /// ------ /// Error /// When the state cannot be created using the combination of input. -#[pyclass(name = "State")] +#[pyclass(name = "State", from_py_object)] #[derive(Clone)] pub struct PyState(pub State, ResidualModel>>>); diff --git a/py-feos/src/user_defined.rs b/py-feos/src/user_defined.rs index 641238f0b..6da88fd30 100644 --- a/py-feos/src/user_defined.rs +++ b/py-feos/src/user_defined.rs @@ -176,7 +176,7 @@ macro_rules! impl_residual { macro_rules! state { ($py_state_id:ident, $py_hd_id:ident, $hd_ty:ty) => { - #[pyclass] + #[pyclass(from_py_object)] #[derive(Clone)] struct $py_state_id(StateHD<$hd_ty>); @@ -233,7 +233,7 @@ macro_rules! state { macro_rules! dual_number { ($py_hd_id:ident, $hd_ty:ty, $py_field_ty:ty) => { - #[pyclass] + #[pyclass(from_py_object)] #[derive(Clone)] struct $py_hd_id($hd_ty); impl_dual_num!($py_hd_id, $hd_ty, $py_field_ty); From af224561c7068dc78901fb79b7b14f919190048e Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Tue, 16 Jun 2026 08:39:14 +0200 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbcd5a069..6a604e2db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Packaging - Updated `quantity` dependency to 0.13 and removed the `typenum` dependency. [#328](https://github.com/feos-org/feos/pull/328) - Added `csv` as a `feos-core` dependency for the new dataset module. [#358](https://github.com/feos-org/feos/pull/358) +- Updated `pyo3`, `pythonize` and `numpy` dependencies to 0.29. [#360](https://github.com/feos-org/feos/pull/360) +- Updated `quantity` and `num-dual` dependencies to 0.14. [#360](https://github.com/feos-org/feos/pull/360) +- Updated `nalgebra` dependency to 0.35. [#360](https://github.com/feos-org/feos/pull/360) +- Updated `gauss-quad` dependency to 0.3. [#360](https://github.com/feos-org/feos/pull/360) ## [Unreleased] ### Added