calphy 1.4.2__tar.gz → 1.4.4__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.
- {calphy-1.4.2/calphy.egg-info → calphy-1.4.4}/PKG-INFO +1 -1
- {calphy-1.4.2 → calphy-1.4.4}/calphy/__init__.py +1 -1
- {calphy-1.4.2 → calphy-1.4.4}/calphy/input.py +58 -1
- {calphy-1.4.2 → calphy-1.4.4}/calphy/integrators.py +7 -2
- {calphy-1.4.2 → calphy-1.4.4}/calphy/phase.py +6 -1
- {calphy-1.4.2 → calphy-1.4.4}/calphy/phase_diagram.py +4 -1
- {calphy-1.4.2 → calphy-1.4.4}/calphy/postprocessing.py +1 -1
- {calphy-1.4.2 → calphy-1.4.4}/calphy/solid.py +1 -1
- {calphy-1.4.2 → calphy-1.4.4/calphy.egg-info}/PKG-INFO +1 -1
- {calphy-1.4.2 → calphy-1.4.4}/calphy.egg-info/SOURCES.txt +1 -0
- {calphy-1.4.2 → calphy-1.4.4}/setup.py +1 -1
- calphy-1.4.4/tests/test_ex07.py +25 -0
- {calphy-1.4.2 → calphy-1.4.4}/LICENSE +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/MANIFEST.in +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/README.md +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/alchemy.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/clitools.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/composition_transformation.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/errors.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/helpers.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/kernel.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/liquid.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/queuekernel.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/routines.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/scheduler.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/splines.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy/utils.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy.egg-info/dependency_links.txt +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy.egg-info/entry_points.txt +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy.egg-info/not-zip-safe +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy.egg-info/requires.txt +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/calphy.egg-info/top_level.txt +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/setup.cfg +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/tests/test_helpers.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/tests/test_integrators.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/tests/test_options.py +0 -0
- {calphy-1.4.2 → calphy-1.4.4}/tests/test_solid_methods.py +0 -0
|
@@ -28,6 +28,7 @@ from pydantic import (
|
|
|
28
28
|
Field,
|
|
29
29
|
ValidationError,
|
|
30
30
|
model_validator,
|
|
31
|
+
field_validator,
|
|
31
32
|
conlist,
|
|
32
33
|
PrivateAttr,
|
|
33
34
|
)
|
|
@@ -48,7 +49,7 @@ from pyscal3.core import structure_dict, element_dict, _make_crystal
|
|
|
48
49
|
from ase.io import read, write
|
|
49
50
|
import shutil
|
|
50
51
|
|
|
51
|
-
__version__ = "1.4.
|
|
52
|
+
__version__ = "1.4.4"
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
def _check_equal(val):
|
|
@@ -180,6 +181,22 @@ class MeltingTemperature(BaseModel, title="Input options for melting temperature
|
|
|
180
181
|
step: Annotated[int, Field(default=200, ge=20)]
|
|
181
182
|
attempts: Annotated[int, Field(default=5, ge=1)]
|
|
182
183
|
|
|
184
|
+
class MaterialsProject(BaseModel, title='Input options for materials project'):
|
|
185
|
+
api_key: Annotated[str, Field(default="", exclude=True)]
|
|
186
|
+
conventional: Annotated[bool, Field(default=True)]
|
|
187
|
+
target_natoms: Annotated[int, Field(default=1500, description='The structure parsed from materials project would be repeated to approximately this value')]
|
|
188
|
+
|
|
189
|
+
@field_validator("api_key", mode="after")
|
|
190
|
+
def resolve_api_key(cls, v: str) -> str:
|
|
191
|
+
if not v:
|
|
192
|
+
return v
|
|
193
|
+
value = os.getenv(v)
|
|
194
|
+
if not value:
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"Environment variable '{v}' not found or empty. "
|
|
197
|
+
f"Set it before running, e.g.:\n export {v}='your_api_key_here'"
|
|
198
|
+
)
|
|
199
|
+
return value
|
|
183
200
|
|
|
184
201
|
class Calculation(BaseModel, title="Main input class"):
|
|
185
202
|
monte_carlo: Optional[MonteCarlo] = MonteCarlo()
|
|
@@ -191,6 +208,8 @@ class Calculation(BaseModel, title="Main input class"):
|
|
|
191
208
|
tolerance: Optional[Tolerance] = Tolerance()
|
|
192
209
|
uhlenbeck_ford_model: Optional[UFMP] = UFMP()
|
|
193
210
|
melting_temperature: Optional[MeltingTemperature] = MeltingTemperature()
|
|
211
|
+
materials_project: Optional[MaterialsProject] = MaterialsProject()
|
|
212
|
+
|
|
194
213
|
element: Annotated[List[str], BeforeValidator(to_list), Field(default=[])]
|
|
195
214
|
n_elements: Annotated[int, Field(default=0)]
|
|
196
215
|
mass: Annotated[List[float], BeforeValidator(to_list), Field(default=[])]
|
|
@@ -496,6 +515,44 @@ class Calculation(BaseModel, title="Main input class"):
|
|
|
496
515
|
self._original_lattice = self.lattice.lower()
|
|
497
516
|
write_structure_file = True
|
|
498
517
|
|
|
518
|
+
elif self.lattice.split('-')[0] == 'mp':
|
|
519
|
+
#confirm here that API key exists
|
|
520
|
+
if not self.materials_project.api_key:
|
|
521
|
+
raise ValueError('could not find API KEY, pls set it.')
|
|
522
|
+
#now we need to fetch the structure
|
|
523
|
+
try:
|
|
524
|
+
from mp_api.client import MPRester
|
|
525
|
+
except ImportError:
|
|
526
|
+
raise ImportError('Could not import mp_api, make sure you install mp_api package!')
|
|
527
|
+
#now all good
|
|
528
|
+
rest = {
|
|
529
|
+
"use_document_model": False,
|
|
530
|
+
"include_user_agent": True,
|
|
531
|
+
"api_key": self.materials_project.api_key,
|
|
532
|
+
}
|
|
533
|
+
with MPRester(**rest) as mpr:
|
|
534
|
+
docs = mpr.materials.summary.search(material_ids=[self.lattice])
|
|
535
|
+
|
|
536
|
+
structures = []
|
|
537
|
+
for doc in docs:
|
|
538
|
+
struct = doc['structure']
|
|
539
|
+
if self.materials_project.conventional:
|
|
540
|
+
aseatoms = struct.to_conventional().to_ase_atoms()
|
|
541
|
+
else:
|
|
542
|
+
aseatoms = struct.to_primitive().to_ase_atoms()
|
|
543
|
+
structures.append(aseatoms)
|
|
544
|
+
structure = structures[0]
|
|
545
|
+
|
|
546
|
+
if np.prod(self.repeat) == 1:
|
|
547
|
+
x = int(np.ceil((self.materials_project.target_natoms/len(structure))**(1/3)))
|
|
548
|
+
structure = structure.repeat(x)
|
|
549
|
+
else:
|
|
550
|
+
structure = structure.repeat(self.repeat)
|
|
551
|
+
|
|
552
|
+
self._natoms = len(structure)
|
|
553
|
+
self._original_lattice = self.lattice.lower()
|
|
554
|
+
write_structure_file = True
|
|
555
|
+
|
|
499
556
|
else:
|
|
500
557
|
# this is a file
|
|
501
558
|
if not os.path.exists(self.lattice):
|
|
@@ -226,6 +226,7 @@ def integrate_rs(simfolder, f0, t,
|
|
|
226
226
|
|
|
227
227
|
"""
|
|
228
228
|
ws = []
|
|
229
|
+
es = []
|
|
229
230
|
p = p/(10000*160.21766208)
|
|
230
231
|
|
|
231
232
|
for i in range(1, nsims+1):
|
|
@@ -245,9 +246,12 @@ def integrate_rs(simfolder, f0, t,
|
|
|
245
246
|
wf = cumtrapz(fdx, flambda,initial=0)
|
|
246
247
|
wb = cumtrapz(bdx[::-1], blambda[::-1],initial=0)
|
|
247
248
|
w = (wf + wb) / (2*flambda)
|
|
249
|
+
e = np.max(np.abs((wf - wb)/(2*flambda)))
|
|
250
|
+
|
|
248
251
|
ws.append(w)
|
|
249
|
-
|
|
252
|
+
es.append(e)
|
|
250
253
|
|
|
254
|
+
e_diss = np.min(es)
|
|
251
255
|
wmean = np.mean(ws, axis=0)
|
|
252
256
|
werr = np.std(ws, axis=0)
|
|
253
257
|
temp = t/flambda
|
|
@@ -257,8 +261,9 @@ def integrate_rs(simfolder, f0, t,
|
|
|
257
261
|
if not return_values:
|
|
258
262
|
outfile = os.path.join(simfolder, "temperature_sweep.dat")
|
|
259
263
|
np.savetxt(outfile, np.column_stack((temp, f, werr)))
|
|
264
|
+
return None, e_diss
|
|
260
265
|
else:
|
|
261
|
-
return (temp, f, werr)
|
|
266
|
+
return (temp, f, werr), e_diss
|
|
262
267
|
|
|
263
268
|
|
|
264
269
|
def integrate_ps(simfolder, f0, natoms, pi, pf, nsims=1,
|
|
@@ -1206,7 +1206,7 @@ class Phase:
|
|
|
1206
1206
|
res : list of lists of shape 1x3
|
|
1207
1207
|
Only returned if `return_values` is True.
|
|
1208
1208
|
"""
|
|
1209
|
-
res = integrate_rs(
|
|
1209
|
+
res, ediss = integrate_rs(
|
|
1210
1210
|
self.simfolder,
|
|
1211
1211
|
self.fe,
|
|
1212
1212
|
self.calc._temperature,
|
|
@@ -1217,6 +1217,11 @@ class Phase:
|
|
|
1217
1217
|
return_values=return_values,
|
|
1218
1218
|
)
|
|
1219
1219
|
|
|
1220
|
+
self.logger.info(f'Maximum energy dissipation along the temperature scaling part: {ediss} eV/atom')
|
|
1221
|
+
if np.abs(ediss) > 1E-4:
|
|
1222
|
+
self.logger.warning(f'Found max energy dissipation of {ediss} along the temperature scaling path. Please ensure there are no structural changes!')
|
|
1223
|
+
|
|
1224
|
+
|
|
1220
1225
|
if return_values:
|
|
1221
1226
|
return res
|
|
1222
1227
|
|
|
@@ -314,7 +314,10 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
|
|
|
314
314
|
|
|
315
315
|
comps = phase['composition']
|
|
316
316
|
reference_element = comps["reference_element"]
|
|
317
|
-
use_composition_scaling
|
|
317
|
+
if "use_composition_scaling" in comps.keys():
|
|
318
|
+
use_composition_scaling = bool(comps["use_composition_scaling"])
|
|
319
|
+
else:
|
|
320
|
+
use_composition_scaling = True
|
|
318
321
|
if str(phase_reference_state) == 'liquid':
|
|
319
322
|
use_composition_scaling = False
|
|
320
323
|
|
|
@@ -121,7 +121,7 @@ def gather_results(mainfolder, reduce_composition=True,
|
|
|
121
121
|
#print(inpfile)
|
|
122
122
|
if not os.path.exists(outfile):
|
|
123
123
|
datadict['status'].append('False')
|
|
124
|
-
datadict['free_energy'].append(np.
|
|
124
|
+
datadict['free_energy'].append(np.nan)
|
|
125
125
|
#check if error file is found
|
|
126
126
|
errfile = os.path.join(os.getcwd(), mainfolder, folder+'.sub.err')
|
|
127
127
|
datadict['error_code'][-1] = _extract_error(errfile)
|
|
@@ -299,7 +299,7 @@ class Solid(cph.Phase):
|
|
|
299
299
|
lmp.command(f'pair_style {self.calc._pair_style_with_options[0]}')
|
|
300
300
|
|
|
301
301
|
#set up structure
|
|
302
|
-
lmp = ph.create_structure(lmp, self.calc
|
|
302
|
+
lmp = ph.create_structure(lmp, self.calc)
|
|
303
303
|
|
|
304
304
|
if self.calc.potential_file is None:
|
|
305
305
|
lmp.command(f'pair_coeff {self.calc.pair_coeff[0]}')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
def test_ex07():
|
|
2
|
+
import calphy
|
|
3
|
+
from calphy.postprocessing import read_report
|
|
4
|
+
|
|
5
|
+
# def test_example07_first_cell_runs():
|
|
6
|
+
# notebook_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../examples/example_07/analysis.ipynb'))
|
|
7
|
+
|
|
8
|
+
# with open(notebook_path, "r", encoding="utf-8") as f:
|
|
9
|
+
# nb = nbformat.read(f, as_version=4)
|
|
10
|
+
|
|
11
|
+
# # Filter just the first code cell
|
|
12
|
+
# first_code_cell = next((cell for cell in nb.cells if cell.cell_type == "code"), None)
|
|
13
|
+
|
|
14
|
+
# if not first_code_cell:
|
|
15
|
+
# pytest.fail("No code cell found in example07.ipynb")
|
|
16
|
+
|
|
17
|
+
# # Create a minimal notebook with only the first cell
|
|
18
|
+
# nb_single = nbformat.v4.new_notebook()
|
|
19
|
+
# nb_single.cells = [first_code_cell]
|
|
20
|
+
|
|
21
|
+
# try:
|
|
22
|
+
# client = NotebookClient(nb_single, timeout=60, kernel_name="pyiron")
|
|
23
|
+
# client.execute()
|
|
24
|
+
# except Exception as e:
|
|
25
|
+
# pytest.fail(f"First code cell in example07.analysis.ipynb failed: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|