peritheos 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- peritheos-0.1.0/LICENSE +21 -0
- peritheos-0.1.0/PKG-INFO +145 -0
- peritheos-0.1.0/README.md +118 -0
- peritheos-0.1.0/peritheos/__init__.py +3 -0
- peritheos-0.1.0/peritheos/constants.py +22 -0
- peritheos-0.1.0/peritheos/eos/__init__.py +234 -0
- peritheos-0.1.0/peritheos/eos/_reference/__init__.py +0 -0
- peritheos-0.1.0/peritheos/eos/_reference/sokolova2016.py +527 -0
- peritheos-0.1.0/peritheos/eos/rt/__init__.py +7 -0
- peritheos-0.1.0/peritheos/eos/rt/bm.py +281 -0
- peritheos-0.1.0/peritheos/eos/rt/holzapfel.py +196 -0
- peritheos-0.1.0/peritheos/eos/rt/vinet.py +86 -0
- peritheos-0.1.0/peritheos/eos/thermal/__init__.py +5 -0
- peritheos-0.1.0/peritheos/eos/thermal/sokolova2016.py +242 -0
- peritheos-0.1.0/peritheos/utils.py +118 -0
- peritheos-0.1.0/peritheos.egg-info/PKG-INFO +145 -0
- peritheos-0.1.0/peritheos.egg-info/SOURCES.txt +26 -0
- peritheos-0.1.0/peritheos.egg-info/dependency_links.txt +1 -0
- peritheos-0.1.0/peritheos.egg-info/requires.txt +2 -0
- peritheos-0.1.0/peritheos.egg-info/top_level.txt +1 -0
- peritheos-0.1.0/pyproject.toml +48 -0
- peritheos-0.1.0/setup.cfg +4 -0
- peritheos-0.1.0/tests/test_bm.py +186 -0
- peritheos-0.1.0/tests/test_eos_api.py +51 -0
- peritheos-0.1.0/tests/test_holzapfel.py +157 -0
- peritheos-0.1.0/tests/test_package_metadata.py +7 -0
- peritheos-0.1.0/tests/test_thermal_sokolova.py +350 -0
- peritheos-0.1.0/tests/test_vinet.py +98 -0
peritheos-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Clemens Prescher
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
peritheos-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: peritheos
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A library for thermodynamic equations of state calculations
|
|
5
|
+
Author-email: Clemens Prescher <clemens.prescher@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cprescher/peritheos
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/cprescher/peritheos/issues
|
|
9
|
+
Keywords: equation-of-state,high-pressure,thermodynamics
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy>=1.20.0
|
|
25
|
+
Requires-Dist: scipy>=1.7.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Peritheos
|
|
29
|
+
|
|
30
|
+
A Python library for thermodynamic equations of state calculations for solid materials.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- Room temperature equations of state (EOS) implementations
|
|
35
|
+
- Birch-Murnaghan
|
|
36
|
+
- Vinet
|
|
37
|
+
- Holzapfel
|
|
38
|
+
- Thermal equations of state (EOS) implementations
|
|
39
|
+
- Sokolova 2016
|
|
40
|
+
|
|
41
|
+
## Unit conventions
|
|
42
|
+
|
|
43
|
+
- Public pressure and bulk-modulus values are in GPa.
|
|
44
|
+
- Temperatures are in K.
|
|
45
|
+
- Birch-Murnaghan and Vinet accept any consistent volume unit.
|
|
46
|
+
- Holzapfel and Sokolova 2016 require molar volume in J bar^-1, which is
|
|
47
|
+
equivalent to cm^3/mol divided by 10.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install peritheos
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The latest development version can instead be installed directly from GitHub:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install git+https://github.com/CPrescher/peritheos.git
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
### Room-temperature equations of state
|
|
64
|
+
|
|
65
|
+
Third-order Birch-Murnaghan equation of state:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from peritheos.eos.rt import BM3
|
|
69
|
+
|
|
70
|
+
# V0 may use any volume unit for a room-temperature EOS; K0 is in GPa here.
|
|
71
|
+
eos = BM3(V0=50, K0=130, K0_prime=4.3)
|
|
72
|
+
|
|
73
|
+
# Calculate pressure and bulk modulus at a given volume.
|
|
74
|
+
pressure = eos.pressure(V=40)
|
|
75
|
+
bulk_modulus = eos.bulk_modulus(V=40)
|
|
76
|
+
|
|
77
|
+
# Invert the EOS to calculate volume at a given pressure.
|
|
78
|
+
volume = eos.volume(P=pressure)
|
|
79
|
+
|
|
80
|
+
print(f"Pressure: {pressure} GPa")
|
|
81
|
+
print(f"Bulk modulus: {bulk_modulus} GPa")
|
|
82
|
+
print(f"Recovered volume: {volume}")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Thermal equations of state
|
|
86
|
+
|
|
87
|
+
Diamond thermal equation of state from sokolova et al. 2016
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from peritheos.eos.rt.holzapfel import Holzapfel
|
|
91
|
+
from peritheos.eos.thermal.sokolova2016 import Sokolova2016
|
|
92
|
+
|
|
93
|
+
# Diamond parameters from Sokolova et al. 2016.
|
|
94
|
+
# The thermal model requires molar volume in J bar^-1 (= [cm^3/mol] / 10),
|
|
95
|
+
# pressure parameters in GPa, and temperatures in K.
|
|
96
|
+
V0 = 0.3414
|
|
97
|
+
K0 = 441.5
|
|
98
|
+
K0_prime = 3.9 # pressure derivative of bulk modulus at reference volume
|
|
99
|
+
QE1o = 684 # first Einstein characteristic temperature
|
|
100
|
+
mE1 = 0.564 # first Einstein number
|
|
101
|
+
QE2o = 1561 # second Einstein characteristic temperature
|
|
102
|
+
mE2 = 2.436 # second Einstein number
|
|
103
|
+
delta = -0.506 # additive normalizing constant for the Gruneisen parameter
|
|
104
|
+
t = 1.085 # generalized Gruneisen parameter
|
|
105
|
+
a_0 = 0 # intrinsic anharmonicity parameter
|
|
106
|
+
m = 0 # anharmonic analogue of the Grüneisen parameter
|
|
107
|
+
e_0 = 0 # free electrons parameter
|
|
108
|
+
g = 0 # electronic analogue of the Grüneisen parameter
|
|
109
|
+
|
|
110
|
+
n = 1 # number of atoms in the formula unit
|
|
111
|
+
z = 6 # atomic number of the formula unit
|
|
112
|
+
Tr = 298.15 # in K - Reference temperature
|
|
113
|
+
|
|
114
|
+
# Initialize the Holzapfel EOS
|
|
115
|
+
holzapfel = Holzapfel(V0=V0, K0=K0, K0_prime=K0_prime, n=n, Z=z)
|
|
116
|
+
|
|
117
|
+
# Initialize the Sokolova 2016 EOS
|
|
118
|
+
sokolova = Sokolova2016(
|
|
119
|
+
rt_eos=holzapfel,
|
|
120
|
+
Tr=Tr,
|
|
121
|
+
QE1o=QE1o,
|
|
122
|
+
mE1=mE1,
|
|
123
|
+
QE2o=QE2o,
|
|
124
|
+
mE2=mE2,
|
|
125
|
+
delta=delta,
|
|
126
|
+
t=t,
|
|
127
|
+
a_0=a_0,
|
|
128
|
+
m=m,
|
|
129
|
+
g=g,
|
|
130
|
+
e_0=e_0,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Calculate the thermal pressure at a given volume and temperature
|
|
134
|
+
V = V0 * 0.8
|
|
135
|
+
T = 3000 # in K
|
|
136
|
+
thermal_pressure = sokolova.thermal_pressure(V, T)
|
|
137
|
+
rt_pressure = holzapfel.pressure(V)
|
|
138
|
+
pressure = sokolova.pressure(V, T)
|
|
139
|
+
recovered_volume = sokolova.volume(pressure, T)
|
|
140
|
+
|
|
141
|
+
print(f"Thermal pressure: {thermal_pressure} GPa")
|
|
142
|
+
print(f"RT pressure: {rt_pressure} GPa")
|
|
143
|
+
print(f"Total pressure: {pressure} GPa")
|
|
144
|
+
print(f"Recovered volume: {recovered_volume} J bar^-1")
|
|
145
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Peritheos
|
|
2
|
+
|
|
3
|
+
A Python library for thermodynamic equations of state calculations for solid materials.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Room temperature equations of state (EOS) implementations
|
|
8
|
+
- Birch-Murnaghan
|
|
9
|
+
- Vinet
|
|
10
|
+
- Holzapfel
|
|
11
|
+
- Thermal equations of state (EOS) implementations
|
|
12
|
+
- Sokolova 2016
|
|
13
|
+
|
|
14
|
+
## Unit conventions
|
|
15
|
+
|
|
16
|
+
- Public pressure and bulk-modulus values are in GPa.
|
|
17
|
+
- Temperatures are in K.
|
|
18
|
+
- Birch-Murnaghan and Vinet accept any consistent volume unit.
|
|
19
|
+
- Holzapfel and Sokolova 2016 require molar volume in J bar^-1, which is
|
|
20
|
+
equivalent to cm^3/mol divided by 10.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install peritheos
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The latest development version can instead be installed directly from GitHub:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install git+https://github.com/CPrescher/peritheos.git
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Room-temperature equations of state
|
|
37
|
+
|
|
38
|
+
Third-order Birch-Murnaghan equation of state:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from peritheos.eos.rt import BM3
|
|
42
|
+
|
|
43
|
+
# V0 may use any volume unit for a room-temperature EOS; K0 is in GPa here.
|
|
44
|
+
eos = BM3(V0=50, K0=130, K0_prime=4.3)
|
|
45
|
+
|
|
46
|
+
# Calculate pressure and bulk modulus at a given volume.
|
|
47
|
+
pressure = eos.pressure(V=40)
|
|
48
|
+
bulk_modulus = eos.bulk_modulus(V=40)
|
|
49
|
+
|
|
50
|
+
# Invert the EOS to calculate volume at a given pressure.
|
|
51
|
+
volume = eos.volume(P=pressure)
|
|
52
|
+
|
|
53
|
+
print(f"Pressure: {pressure} GPa")
|
|
54
|
+
print(f"Bulk modulus: {bulk_modulus} GPa")
|
|
55
|
+
print(f"Recovered volume: {volume}")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Thermal equations of state
|
|
59
|
+
|
|
60
|
+
Diamond thermal equation of state from sokolova et al. 2016
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from peritheos.eos.rt.holzapfel import Holzapfel
|
|
64
|
+
from peritheos.eos.thermal.sokolova2016 import Sokolova2016
|
|
65
|
+
|
|
66
|
+
# Diamond parameters from Sokolova et al. 2016.
|
|
67
|
+
# The thermal model requires molar volume in J bar^-1 (= [cm^3/mol] / 10),
|
|
68
|
+
# pressure parameters in GPa, and temperatures in K.
|
|
69
|
+
V0 = 0.3414
|
|
70
|
+
K0 = 441.5
|
|
71
|
+
K0_prime = 3.9 # pressure derivative of bulk modulus at reference volume
|
|
72
|
+
QE1o = 684 # first Einstein characteristic temperature
|
|
73
|
+
mE1 = 0.564 # first Einstein number
|
|
74
|
+
QE2o = 1561 # second Einstein characteristic temperature
|
|
75
|
+
mE2 = 2.436 # second Einstein number
|
|
76
|
+
delta = -0.506 # additive normalizing constant for the Gruneisen parameter
|
|
77
|
+
t = 1.085 # generalized Gruneisen parameter
|
|
78
|
+
a_0 = 0 # intrinsic anharmonicity parameter
|
|
79
|
+
m = 0 # anharmonic analogue of the Grüneisen parameter
|
|
80
|
+
e_0 = 0 # free electrons parameter
|
|
81
|
+
g = 0 # electronic analogue of the Grüneisen parameter
|
|
82
|
+
|
|
83
|
+
n = 1 # number of atoms in the formula unit
|
|
84
|
+
z = 6 # atomic number of the formula unit
|
|
85
|
+
Tr = 298.15 # in K - Reference temperature
|
|
86
|
+
|
|
87
|
+
# Initialize the Holzapfel EOS
|
|
88
|
+
holzapfel = Holzapfel(V0=V0, K0=K0, K0_prime=K0_prime, n=n, Z=z)
|
|
89
|
+
|
|
90
|
+
# Initialize the Sokolova 2016 EOS
|
|
91
|
+
sokolova = Sokolova2016(
|
|
92
|
+
rt_eos=holzapfel,
|
|
93
|
+
Tr=Tr,
|
|
94
|
+
QE1o=QE1o,
|
|
95
|
+
mE1=mE1,
|
|
96
|
+
QE2o=QE2o,
|
|
97
|
+
mE2=mE2,
|
|
98
|
+
delta=delta,
|
|
99
|
+
t=t,
|
|
100
|
+
a_0=a_0,
|
|
101
|
+
m=m,
|
|
102
|
+
g=g,
|
|
103
|
+
e_0=e_0,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Calculate the thermal pressure at a given volume and temperature
|
|
107
|
+
V = V0 * 0.8
|
|
108
|
+
T = 3000 # in K
|
|
109
|
+
thermal_pressure = sokolova.thermal_pressure(V, T)
|
|
110
|
+
rt_pressure = holzapfel.pressure(V)
|
|
111
|
+
pressure = sokolova.pressure(V, T)
|
|
112
|
+
recovered_volume = sokolova.volume(pressure, T)
|
|
113
|
+
|
|
114
|
+
print(f"Thermal pressure: {thermal_pressure} GPa")
|
|
115
|
+
print(f"RT pressure: {rt_pressure} GPa")
|
|
116
|
+
print(f"Total pressure: {pressure} GPa")
|
|
117
|
+
print(f"Recovered volume: {recovered_volume} J bar^-1")
|
|
118
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Physical constants used in thermodynamic calculations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from scipy import constants
|
|
6
|
+
|
|
7
|
+
# Universal gas constant (J/(mol·K))
|
|
8
|
+
R = constants.R
|
|
9
|
+
|
|
10
|
+
# Boltzmann constant (J/K)
|
|
11
|
+
k_B = constants.Boltzmann
|
|
12
|
+
|
|
13
|
+
# Avogadro's number (1/mol)
|
|
14
|
+
N_A = constants.Avogadro
|
|
15
|
+
|
|
16
|
+
# Standard temperature and pressure
|
|
17
|
+
STP_TEMPERATURE = constants.zero_Celsius # K (273.15)
|
|
18
|
+
STP_PRESSURE = constants.atm # Pa (101325.0)
|
|
19
|
+
|
|
20
|
+
# Other useful constants
|
|
21
|
+
STANDARD_GRAVITY = constants.g # m/s²
|
|
22
|
+
STEFAN_BOLTZMANN = constants.Stefan_Boltzmann # W/(m²·K⁴)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Equations of state module for Peritheos
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Callable, Union
|
|
6
|
+
import numpy as np
|
|
7
|
+
from numpy.typing import NDArray
|
|
8
|
+
from scipy import optimize
|
|
9
|
+
|
|
10
|
+
# Type alias for numeric values (scalar or array)
|
|
11
|
+
NumericType = Union[float, NDArray[np.float64]]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_finite_scalar(value: float, name: str) -> float:
|
|
15
|
+
"""Return *value* as a finite float or raise a descriptive error."""
|
|
16
|
+
value = float(value)
|
|
17
|
+
if not np.isfinite(value):
|
|
18
|
+
raise ValueError(f"{name} must be finite")
|
|
19
|
+
return value
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_positive_scalar(value: float, name: str) -> float:
|
|
23
|
+
"""Return *value* as a positive finite float."""
|
|
24
|
+
value = validate_finite_scalar(value, name)
|
|
25
|
+
if value <= 0:
|
|
26
|
+
raise ValueError(f"{name} must be greater than zero")
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def validate_volume(V: NumericType) -> NumericType:
|
|
31
|
+
"""Validate volume input while preserving scalar or array behaviour."""
|
|
32
|
+
values = np.asarray(V, dtype=float)
|
|
33
|
+
if not np.all(np.isfinite(values)):
|
|
34
|
+
raise ValueError("Volume must be finite")
|
|
35
|
+
if np.any(values <= 0):
|
|
36
|
+
raise ValueError("Volume must be greater than zero")
|
|
37
|
+
if values.ndim == 0:
|
|
38
|
+
return float(values)
|
|
39
|
+
return values
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _scalar_pressure(pressure_function: Callable[[float], NumericType], V: float) -> float:
|
|
43
|
+
"""Evaluate a pressure callable and require a finite scalar result."""
|
|
44
|
+
pressure = np.asarray(pressure_function(V), dtype=float)
|
|
45
|
+
if pressure.ndim != 0:
|
|
46
|
+
raise TypeError("The pressure function must return a scalar for scalar volume")
|
|
47
|
+
result = float(pressure)
|
|
48
|
+
if not np.isfinite(result):
|
|
49
|
+
raise ArithmeticError(f"EOS returned a non-finite pressure at V={V}")
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def solve_volume(
|
|
54
|
+
pressure_function: Callable[[float], NumericType],
|
|
55
|
+
pressure: float,
|
|
56
|
+
reference_volume: float,
|
|
57
|
+
) -> float:
|
|
58
|
+
"""Solve ``pressure_function(V) == pressure`` on the branch nearest V0.
|
|
59
|
+
|
|
60
|
+
The EOS is assumed to be locally monotonic around its reference volume:
|
|
61
|
+
pressure increases on compression and decreases on expansion. Starting at
|
|
62
|
+
``V0``, the search geometrically decreases volume for a higher target
|
|
63
|
+
pressure or increases it for a lower target pressure until the pressure
|
|
64
|
+
residual changes sign. ``scipy.optimize.brentq`` then solves inside that
|
|
65
|
+
positive-volume bracket. If no sign change is found, the requested state is
|
|
66
|
+
reported as outside the model's invertible range.
|
|
67
|
+
"""
|
|
68
|
+
target = validate_finite_scalar(pressure, "Pressure")
|
|
69
|
+
V0 = validate_positive_scalar(reference_volume, "Reference volume")
|
|
70
|
+
p0 = _scalar_pressure(pressure_function, V0)
|
|
71
|
+
f0 = p0 - target
|
|
72
|
+
pressure_tolerance = 1e-10 * max(1.0, abs(target), abs(p0))
|
|
73
|
+
if abs(f0) <= pressure_tolerance:
|
|
74
|
+
return V0
|
|
75
|
+
|
|
76
|
+
if f0 < 0:
|
|
77
|
+
# The target is above P(V0), so search towards compression.
|
|
78
|
+
upper, f_upper = V0, f0
|
|
79
|
+
lower = V0
|
|
80
|
+
f_lower = f0
|
|
81
|
+
for _ in range(160):
|
|
82
|
+
lower *= 0.8
|
|
83
|
+
if lower <= V0 * 1e-14:
|
|
84
|
+
break
|
|
85
|
+
f_lower = _scalar_pressure(pressure_function, lower) - target
|
|
86
|
+
if f_lower >= 0:
|
|
87
|
+
break
|
|
88
|
+
else:
|
|
89
|
+
f_lower = np.nan
|
|
90
|
+
if not np.isfinite(f_lower) or f_lower < 0:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Could not bracket a positive volume for pressure {target}"
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
# The target is below P(V0), so search along the first expansion branch.
|
|
96
|
+
lower, f_lower = V0, f0
|
|
97
|
+
upper = V0
|
|
98
|
+
f_upper = f0
|
|
99
|
+
for _ in range(160):
|
|
100
|
+
upper *= 1.05
|
|
101
|
+
if upper >= V0 * 1e4:
|
|
102
|
+
break
|
|
103
|
+
f_upper = _scalar_pressure(pressure_function, upper) - target
|
|
104
|
+
if f_upper <= 0:
|
|
105
|
+
break
|
|
106
|
+
else:
|
|
107
|
+
f_upper = np.nan
|
|
108
|
+
if not np.isfinite(f_upper) or f_upper > 0:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"Pressure {target} is outside the invertible expansion range"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
result = optimize.brentq(
|
|
114
|
+
lambda volume: _scalar_pressure(pressure_function, volume) - target,
|
|
115
|
+
lower,
|
|
116
|
+
upper,
|
|
117
|
+
xtol=max(np.finfo(float).eps * V0, 1e-14),
|
|
118
|
+
rtol=1e-12,
|
|
119
|
+
)
|
|
120
|
+
residual = abs(_scalar_pressure(pressure_function, result) - target)
|
|
121
|
+
if residual > 1e-8 * max(1.0, abs(target)):
|
|
122
|
+
raise ArithmeticError(
|
|
123
|
+
f"Volume inversion did not converge to the requested pressure; residual={residual}"
|
|
124
|
+
)
|
|
125
|
+
return float(result)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class EosBase:
|
|
129
|
+
"""
|
|
130
|
+
Base class for equation of state implementations.
|
|
131
|
+
|
|
132
|
+
This abstract class defines the interface that all equation of state
|
|
133
|
+
implementations should follow.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def pressure(self, V: NumericType) -> NumericType:
|
|
137
|
+
"""
|
|
138
|
+
Calculate pressure at a given volume.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
V : float or numpy.ndarray
|
|
143
|
+
Volume (in cubic angstroms or any consistent unit)
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
float or numpy.ndarray
|
|
148
|
+
Pressure (in the same units as K0)
|
|
149
|
+
"""
|
|
150
|
+
raise NotImplementedError("Subclasses must implement the pressure method.")
|
|
151
|
+
|
|
152
|
+
def bulk_modulus(self, V: NumericType) -> NumericType:
|
|
153
|
+
"""
|
|
154
|
+
Calculate the bulk modulus.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
V : float or numpy.ndarray
|
|
159
|
+
Volume (in cubic angstroms or any consistent unit)
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
float or numpy.ndarray
|
|
164
|
+
Bulk modulus (in the same units as K0)
|
|
165
|
+
"""
|
|
166
|
+
raise NotImplementedError("Subclasses must implement the bulk_modulus method.")
|
|
167
|
+
|
|
168
|
+
def calculate_volume(self, P: NumericType) -> NumericType:
|
|
169
|
+
"""
|
|
170
|
+
Calculate volume at a given pressure using a bracketed root solver.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
P : float or numpy.ndarray
|
|
175
|
+
Pressure (in the same units as K0)
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
float or numpy.ndarray
|
|
180
|
+
Volume (in the same units as V0)
|
|
181
|
+
"""
|
|
182
|
+
pressures = np.asarray(P, dtype=float)
|
|
183
|
+
if not np.all(np.isfinite(pressures)):
|
|
184
|
+
raise ValueError("Pressure must be finite")
|
|
185
|
+
if pressures.ndim == 0:
|
|
186
|
+
return solve_volume(self.pressure, float(pressures), self.V0)
|
|
187
|
+
return np.array(
|
|
188
|
+
[solve_volume(self.pressure, value, self.V0) for value in pressures.flat]
|
|
189
|
+
).reshape(pressures.shape)
|
|
190
|
+
|
|
191
|
+
def volume(self, P: NumericType) -> NumericType:
|
|
192
|
+
"""Alias for :meth:`calculate_volume` using the P-to-V terminology."""
|
|
193
|
+
return self.calculate_volume(P)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class ThermalEOS(EosBase):
|
|
197
|
+
def __init__(self, rt_eos: EosBase):
|
|
198
|
+
self.rt_eos = rt_eos
|
|
199
|
+
|
|
200
|
+
def thermal_pressure(self, V: NumericType, T: NumericType) -> NumericType:
|
|
201
|
+
raise NotImplementedError("This method should be implemented by the subclass")
|
|
202
|
+
|
|
203
|
+
def pressure(self, V: NumericType, T: NumericType) -> NumericType:
|
|
204
|
+
return self.thermal_pressure(V, T) + self.rt_eos.pressure(V)
|
|
205
|
+
|
|
206
|
+
def calculate_volume(self, P: NumericType, T: NumericType) -> NumericType:
|
|
207
|
+
"""Calculate volume at pressure and temperature using bracketed roots."""
|
|
208
|
+
pressures, temperatures = np.broadcast_arrays(
|
|
209
|
+
np.asarray(P, dtype=float), np.asarray(T, dtype=float)
|
|
210
|
+
)
|
|
211
|
+
if not np.all(np.isfinite(pressures)):
|
|
212
|
+
raise ValueError("Pressure must be finite")
|
|
213
|
+
if not np.all(np.isfinite(temperatures)) or np.any(temperatures <= 0):
|
|
214
|
+
raise ValueError("Temperature must be finite and greater than zero")
|
|
215
|
+
|
|
216
|
+
volumes = np.array(
|
|
217
|
+
[
|
|
218
|
+
solve_volume(
|
|
219
|
+
lambda volume, temperature=float(temperature): self.pressure(
|
|
220
|
+
volume, temperature
|
|
221
|
+
),
|
|
222
|
+
float(pressure),
|
|
223
|
+
self.rt_eos.V0,
|
|
224
|
+
)
|
|
225
|
+
for pressure, temperature in zip(pressures.flat, temperatures.flat)
|
|
226
|
+
]
|
|
227
|
+
).reshape(pressures.shape)
|
|
228
|
+
if volumes.ndim == 0:
|
|
229
|
+
return float(volumes)
|
|
230
|
+
return volumes
|
|
231
|
+
|
|
232
|
+
def volume(self, P: NumericType, T: NumericType) -> NumericType:
|
|
233
|
+
"""Alias for :meth:`calculate_volume` for a thermal EOS."""
|
|
234
|
+
return self.calculate_volume(P, T)
|
|
File without changes
|