calphy 1.3.11__py3-none-any.whl → 1.4.2__py3-none-any.whl

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/input.py CHANGED
@@ -3,14 +3,14 @@ calphy: a Python library and command line interface for automated free
3
3
  energy calculations.
4
4
 
5
5
  Copyright 2021 (c) Sarath Menon^1, Yury Lysogorskiy^2, Ralf Drautz^2
6
- ^1: Max Planck Institut für Eisenforschung, Dusseldorf, Germany
6
+ ^1: Max Planck Institut für Eisenforschung, Dusseldorf, Germany
7
7
  ^2: Ruhr-University Bochum, Bochum, Germany
8
8
 
9
- calphy is published and distributed under the Academic Software License v1.0 (ASL).
10
- calphy is distributed in the hope that it will be useful for non-commercial academic research,
11
- but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ calphy is published and distributed under the Academic Software License v1.0 (ASL).
10
+ calphy is distributed in the hope that it will be useful for non-commercial academic research,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
12
  calphy API is published and distributed under the BSD 3-Clause "New" or "Revised" License
13
- See the LICENSE FILE for more details.
13
+ See the LICENSE FILE for more details.
14
14
 
15
15
  More information about the program can be found in:
16
16
  Menon, Sarath, Yury Lysogorskiy, Jutta Rogal, and Ralf Drautz.
@@ -23,10 +23,18 @@ sarath.menon@ruhr-uni-bochum.de/yury.lysogorskiy@icams.rub.de
23
23
 
24
24
  from typing_extensions import Annotated
25
25
  from typing import Any, Callable, List, ClassVar, Optional, Union
26
- from pydantic import BaseModel, Field, ValidationError, model_validator, conlist, PrivateAttr
26
+ from pydantic import (
27
+ BaseModel,
28
+ Field,
29
+ ValidationError,
30
+ model_validator,
31
+ conlist,
32
+ PrivateAttr,
33
+ )
27
34
  from pydantic.functional_validators import AfterValidator, BeforeValidator
28
35
  from annotated_types import Len
29
36
  import mendeleev
37
+ from tqdm import tqdm
30
38
 
31
39
  import yaml
32
40
  import numpy as np
@@ -40,21 +48,25 @@ from pyscal3.core import structure_dict, element_dict, _make_crystal
40
48
  from ase.io import read, write
41
49
  import shutil
42
50
 
43
- __version__ = "1.3.11"
51
+ __version__ = "1.4.2"
52
+
44
53
 
45
54
  def _check_equal(val):
46
- if not (val[0]==val[1]==val[2]):
55
+ if not (val[0] == val[1] == val[2]):
47
56
  return False
48
57
  return True
49
58
 
59
+
50
60
  def to_list(v: Any) -> List[Any]:
51
61
  return np.atleast_1d(v)
52
62
 
63
+
53
64
  def _to_str(val):
54
65
  if np.isscalar(val):
55
66
  return str(val)
56
67
  else:
57
- return [str(x) for x in val]
68
+ return [str(x) for x in val]
69
+
58
70
 
59
71
  def _to_int(val):
60
72
  if np.isscalar(val):
@@ -62,49 +74,91 @@ def _to_int(val):
62
74
  else:
63
75
  return [int(x) for x in val]
64
76
 
77
+
65
78
  def _to_none(val):
66
- if val in ['none', 'None',]:
67
- return None
79
+ if val in [
80
+ "none",
81
+ "None",
82
+ ]:
83
+ return None
68
84
  return val
69
85
 
86
+
70
87
  def _to_float(val):
71
88
  if np.isscalar(val):
72
89
  return float(val)
73
90
  else:
74
- return [float(x) for x in val]
75
-
76
- class CompositionScaling(BaseModel, title='Composition scaling input options'):
91
+ return [float(x) for x in val]
92
+
93
+
94
+ class UFMP(BaseModel, title="UFM potential input options"):
95
+ p: Annotated[float, Field(default=50.0)]
96
+ sigma: Annotated[float, Field(default=1.5)]
97
+
98
+
99
+ class MonteCarlo(
100
+ BaseModel, title="Options for Monte Carlo moves during particle swap moves"
101
+ ):
102
+ n_steps: Annotated[
103
+ int, Field(default=1, gt=0, description="perform swap moves every n_step")
104
+ ]
105
+ n_swaps: Annotated[
106
+ int, Field(default=0, ge=0, description="number of swap moves to perform")
107
+ ]
108
+ reverse_swap: Annotated[bool, Field(default=True)]
109
+ swap_types: Annotated[
110
+ conlist(int, min_length=2, max_length=2),
111
+ Field(default=[1, 2], description="which atoms to swap between"),
112
+ ]
113
+
114
+
115
+ class CompositionScaling(BaseModel, title="Composition scaling input options"):
77
116
  _input_chemical_composition: PrivateAttr(default=None)
78
117
  output_chemical_composition: Annotated[dict, Field(default={}, required=False)]
79
- restrictions: Annotated[List[str], BeforeValidator(to_list),
80
- Field(default=[], required=False)]
81
-
82
- class MD(BaseModel, title='MD specific input options'):
83
- timestep: Annotated[float, Field(default=0.001,
84
- description='timestep for md simulation',
85
- example='timestep: 0.001'),]
118
+ restrictions: Annotated[
119
+ List[str], BeforeValidator(to_list), Field(default=[], required=False)
120
+ ]
121
+
122
+
123
+ class MD(BaseModel, title="MD specific input options"):
124
+ timestep: Annotated[
125
+ float,
126
+ Field(
127
+ default=0.001,
128
+ description="timestep for md simulation",
129
+ example="timestep: 0.001",
130
+ ),
131
+ ]
86
132
  n_small_steps: Annotated[int, Field(default=10000, gt=0)]
87
133
  n_every_steps: Annotated[int, Field(default=10, gt=0)]
88
134
  n_repeat_steps: Annotated[int, Field(default=10, gt=0)]
89
135
  n_cycles: Annotated[int, Field(default=100, gt=0)]
90
- thermostat_damping: Annotated[Union[float, conlist(float, min_length=2, max_length=2)], Field(default=0.1, gt=0)]
91
- barostat_damping: Annotated[Union[float, conlist(float, min_length=2, max_length=2)], Field(default=0.1, gt=0)]
136
+ thermostat_damping: Annotated[
137
+ Union[float, conlist(float, min_length=2, max_length=2)],
138
+ Field(default=0.1, gt=0),
139
+ ]
140
+ barostat_damping: Annotated[
141
+ Union[float, conlist(float, min_length=2, max_length=2)],
142
+ Field(default=0.1, gt=0),
143
+ ]
92
144
  cmdargs: Annotated[str, Field(default="")]
93
145
  init_commands: Annotated[List, Field(default=[])]
94
146
 
95
147
 
96
- class NoseHoover(BaseModel, title='Specific input options for Nose-Hoover thermostat'):
148
+ class NoseHoover(BaseModel, title="Specific input options for Nose-Hoover thermostat"):
97
149
  thermostat_damping: Annotated[float, Field(default=0.1, gt=0)]
98
150
  barostat_damping: Annotated[float, Field(default=0.1, gt=0)]
99
151
 
100
- class Berendsen(BaseModel, title='Specific input options for Berendsen thermostat'):
152
+
153
+ class Berendsen(BaseModel, title="Specific input options for Berendsen thermostat"):
101
154
  thermostat_damping: Annotated[float, Field(default=100.0, gt=0)]
102
155
  barostat_damping: Annotated[float, Field(default=100.0, gt=0)]
103
156
 
104
- class Queue(BaseModel, title='Options for configuring queue'):
105
- scheduler: Annotated[str, Field(default='local')]
157
+
158
+ class Queue(BaseModel, title="Options for configuring queue"):
159
+ scheduler: Annotated[str, Field(default="local")]
106
160
  cores: Annotated[int, Field(default=1, gt=0)]
107
- jobname: Annotated[str, Field(default='calphy')]
161
+ jobname: Annotated[str, Field(default="calphy")]
108
162
  walltime: Annotated[str, Field(default="23:59:00")]
109
163
  queuename: Annotated[str, Field(default="")]
110
164
  memory: Annotated[str, Field(default="3GB")]
@@ -112,102 +166,127 @@ class Queue(BaseModel, title='Options for configuring queue'):
112
166
  options: Annotated[List, Field(default=[])]
113
167
  modules: Annotated[List, Field(default=[])]
114
168
 
115
- class Tolerance(BaseModel, title='Tolerance settings for convergence'):
169
+
170
+ class Tolerance(BaseModel, title="Tolerance settings for convergence"):
116
171
  lattice_constant: Annotated[float, Field(default=0.0002, ge=0)]
117
172
  spring_constant: Annotated[float, Field(default=0.1, gt=0)]
118
173
  solid_fraction: Annotated[float, Field(default=0.7, ge=0)]
119
174
  liquid_fraction: Annotated[float, Field(default=0.05, ge=0)]
120
175
  pressure: Annotated[float, Field(default=0.5, ge=0)]
121
176
 
122
- class MeltingTemperature(BaseModel, title='Input options for melting temperature mode'):
177
+
178
+ class MeltingTemperature(BaseModel, title="Input options for melting temperature mode"):
123
179
  guess: Annotated[Union[float, None], Field(default=None, gt=0)]
124
180
  step: Annotated[int, Field(default=200, ge=20)]
125
181
  attempts: Annotated[int, Field(default=5, ge=1)]
126
182
 
127
- class Calculation(BaseModel, title='Main input class'):
183
+
184
+ class Calculation(BaseModel, title="Main input class"):
185
+ monte_carlo: Optional[MonteCarlo] = MonteCarlo()
128
186
  composition_scaling: Optional[CompositionScaling] = CompositionScaling()
129
187
  md: Optional[MD] = MD()
130
188
  nose_hoover: Optional[NoseHoover] = NoseHoover()
131
189
  berendsen: Optional[Berendsen] = Berendsen()
132
190
  queue: Optional[Queue] = Queue()
133
- tolerance: Optional[Tolerance] = Tolerance()
134
- melting_temperature: Optional[MeltingTemperature] = MeltingTemperature()
135
- element: Annotated[List[str], BeforeValidator(to_list),
136
- Field(default=[])]
191
+ tolerance: Optional[Tolerance] = Tolerance()
192
+ uhlenbeck_ford_model: Optional[UFMP] = UFMP()
193
+ melting_temperature: Optional[MeltingTemperature] = MeltingTemperature()
194
+ element: Annotated[List[str], BeforeValidator(to_list), Field(default=[])]
137
195
  n_elements: Annotated[int, Field(default=0)]
138
- mass: Annotated[List[float], BeforeValidator(to_list),
139
- Field(default=[])]
196
+ mass: Annotated[List[float], BeforeValidator(to_list), Field(default=[])]
140
197
  _element_dict: dict = PrivateAttr(default={})
141
198
  kernel: Annotated[int, Field(default=0)]
142
- inputfile: Annotated[str, Field(default='')]
199
+ inputfile: Annotated[str, Field(default="")]
143
200
 
144
201
  mode: Annotated[Union[str, None], Field(default=None)]
145
202
  lattice: Annotated[str, Field(default="")]
146
- file_format: Annotated[str, Field(default='lammps-data')]
147
-
148
- #pressure properties
149
- pressure: Annotated[ Union[None, float, conlist(float, min_length=1, max_length=2), conlist(conlist(float, min_length=3, max_length=3), min_length=1, max_length=2)] ,
150
- Field(default=0)]
151
-
203
+ file_format: Annotated[str, Field(default="lammps-data")]
204
+
205
+ # pressure properties
206
+ pressure: Annotated[
207
+ Union[
208
+ None,
209
+ float,
210
+ conlist(float, min_length=1, max_length=2),
211
+ conlist(
212
+ conlist(float, min_length=3, max_length=3), min_length=1, max_length=2
213
+ ),
214
+ ],
215
+ Field(default=0),
216
+ ]
217
+
218
+ _pressure_stop: float = PrivateAttr(default=None)
219
+ _pressure: float = PrivateAttr(default=None)
152
220
  _pressure_stop: float = PrivateAttr(default=None)
153
- _pressure: float = PrivateAttr(default=None)
154
- _pressure_stop: float = PrivateAttr(default=None)
155
221
  _pressure_input: Any = PrivateAttr(default=None)
156
222
  _iso: bool = PrivateAttr(default=False)
157
223
  _fix_lattice: bool = PrivateAttr(default=False)
158
224
 
159
- temperature: Annotated[ Union[float, conlist(float, min_length=1, max_length=2)],
160
- Field(default=0)]
161
- temperature_high: Annotated[ float, Field(default=0.0)]
225
+ temperature: Annotated[
226
+ Union[float, conlist(float, min_length=1, max_length=2)], Field(default=0)
227
+ ]
228
+ temperature_high: Annotated[float, Field(default=0.0)]
162
229
  _temperature: float = PrivateAttr(default=None)
163
230
  _temperature_high: float = PrivateAttr(default=None)
164
231
  _temperature_stop: float = PrivateAttr(default=None)
165
232
  _temperature_input: float = PrivateAttr(default=None)
166
-
167
- melting_cycle: Annotated[ bool, Field(default=True)]
168
-
169
- pair_style: Annotated[ Union[List[str], None], BeforeValidator(to_list),
170
- Field(default=None)]
171
- pair_coeff: Annotated[ Union[List[str], None], BeforeValidator(to_list),
172
- Field(default=None)]
173
- potential_file: Annotated[ Union[str,None], Field(default=None)]
233
+
234
+ melting_cycle: Annotated[bool, Field(default=True)]
235
+
236
+ pair_style: Annotated[
237
+ Union[List[str], None], BeforeValidator(to_list), Field(default=None)
238
+ ]
239
+ pair_coeff: Annotated[
240
+ Union[List[str], None], BeforeValidator(to_list), Field(default=None)
241
+ ]
242
+ potential_file: Annotated[Union[str, None], Field(default=None)]
174
243
  fix_potential_path: Annotated[bool, Field(default=True)]
175
244
  _pair_style_with_options: List[str] = PrivateAttr(default=None)
176
-
177
-
178
- reference_phase: Annotated[ str, Field(default = "")]
179
- lattice_constant: Annotated[float, Field(default = 0)]
180
- repeat: Annotated[conlist(int, min_length=3, max_length=3),
181
- Field(default=[1,1,1])]
182
-
183
- script_mode: Annotated[ bool, Field(default = False)]
184
- lammps_executable: Annotated[ Union[str,None], Field(default = None)]
185
- mpi_executable: Annotated[ Union[str,None], Field(default = None)]
186
-
187
- npt: Annotated[ bool, Field(default = True)]
188
- n_equilibration_steps: Annotated[ int, Field(default = 25000)]
189
- n_switching_steps: Annotated[ Union[int, conlist(int, min_length=2, max_length=2)], Field(default = [50000, 50000])]
245
+
246
+ reference_phase: Annotated[str, Field(default="")]
247
+ lattice_constant: Annotated[float, Field(default=0)]
248
+ repeat: Annotated[
249
+ conlist(int, min_length=3, max_length=3), Field(default=[1, 1, 1])
250
+ ]
251
+
252
+ script_mode: Annotated[bool, Field(default=False)]
253
+ lammps_executable: Annotated[Union[str, None], Field(default=None)]
254
+ mpi_executable: Annotated[Union[str, None], Field(default=None)]
255
+
256
+ npt: Annotated[bool, Field(default=True)]
257
+ n_equilibration_steps: Annotated[int, Field(default=25000)]
258
+ n_switching_steps: Annotated[
259
+ Union[int, conlist(int, min_length=2, max_length=2)],
260
+ Field(default=[50000, 50000]),
261
+ ]
190
262
  _n_switching_steps: int = PrivateAttr(default=50000)
191
263
  _n_sweep_steps: int = PrivateAttr(default=50000)
192
- n_print_steps: Annotated[int, Field(default = 0)]
193
- n_iterations: Annotated[int, Field(default = 1)]
194
- equilibration_control: Annotated[Union[str,None], Field(default = None)]
195
- folder_prefix: Annotated[Union[str,None], Field(default = None)]
264
+ n_print_steps: Annotated[int, Field(default=0)]
265
+ n_iterations: Annotated[int, Field(default=1)]
266
+ equilibration_control: Annotated[Union[str, None], Field(default=None)]
267
+ folder_prefix: Annotated[Union[str, None], Field(default=None)]
196
268
 
197
- #add second level options; for example spring constants
198
- spring_constants: Annotated[Union[List[float],None], Field(default = None)]
269
+ # add second level options; for example spring constants
270
+ spring_constants: Annotated[Union[List[float], None], Field(default=None)]
199
271
 
200
- #structure items
272
+ # some input keywords that will be used for the phase diagram mode
273
+ phase_name: Annotated[str, Field(default="")]
274
+ reference_composition: Annotated[float, Field(default=0.00)]
275
+
276
+ # structure items
201
277
  _structure: Any = PrivateAttr(default=None)
202
278
 
203
- @model_validator(mode='after')
204
- def _validate_all(self) -> 'Input':
279
+ # just check for nlements in compscale
280
+ _totalelements = PrivateAttr(default=0)
281
+
282
+ @model_validator(mode="after")
283
+ def _validate_all(self) -> "Input":
205
284
 
206
285
  if not (len(self.element) == len(self.mass)):
207
- raise ValueError('mass and elements should have same length')
208
-
286
+ raise ValueError("mass and elements should have same length")
287
+
209
288
  self.n_elements = len(self.element)
210
-
289
+
211
290
  self._pressure_input = copy.copy(self.pressure)
212
291
  if self.pressure is None:
213
292
  self._iso = True
@@ -231,25 +310,25 @@ class Calculation(BaseModel, title='Main input class'):
231
310
  self._pressure_stop = self.pressure[1]
232
311
  elif np.shape(self.pressure) == (1, 3):
233
312
  if not _check_equal(self.pressure[0]):
234
- raise ValueError('All pressure terms must be equal')
313
+ raise ValueError("All pressure terms must be equal")
235
314
  self._iso = False
236
315
  self._fix_lattice = False
237
316
  self._pressure = self.pressure[0][0]
238
- self._pressure_stop = self.pressure[0][0]
317
+ self._pressure_stop = self.pressure[0][0]
239
318
  elif np.shape(self.pressure) == (2, 3):
240
319
  if not (_check_equal(self.pressure[0]) and _check_equal(self.pressure[1])):
241
- raise ValueError('All pressure terms must be equal')
320
+ raise ValueError("All pressure terms must be equal")
242
321
  self._iso = False
243
322
  self._fix_lattice = False
244
323
  self._pressure = self.pressure[0][0]
245
- self._pressure_stop = self.pressure[1][0]
324
+ self._pressure_stop = self.pressure[1][0]
246
325
  else:
247
- raise ValueError('Unknown format for pressure')
326
+ raise ValueError("Unknown format for pressure")
248
327
 
249
328
  self._temperature_input = copy.copy(self.temperature)
250
- #guess a melting temp of the system, this will be mostly ignored
251
- #chem = mendeleev.element(self.element[0])
252
- #self._melting_temperature = chem.melting_point
329
+ # guess a melting temp of the system, this will be mostly ignored
330
+ # chem = mendeleev.element(self.element[0])
331
+ # self._melting_temperature = chem.melting_point
253
332
  try:
254
333
  chem = mendeleev.element(self.element[0])
255
334
  self._melting_temperature = chem.melting_point
@@ -257,16 +336,20 @@ class Calculation(BaseModel, title='Main input class'):
257
336
  self._melting_temperature = None
258
337
 
259
338
  if self.temperature == 0:
260
- #the only situation in which it can be None is if mode is melting temp
339
+ # the only situation in which it can be None is if mode is melting temp
261
340
  if len(self.element) > 1:
262
- raise ValueError("Cannot guess start temperature for more than one species, please specify")
263
- #now try to guess
341
+ raise ValueError(
342
+ "Cannot guess start temperature for more than one species, please specify"
343
+ )
344
+ # now try to guess
264
345
  if self._melting_temperature is None:
265
- raise ValueError("Could not guess start temperature for more than one species, please specify")
346
+ raise ValueError(
347
+ "Could not guess start temperature for more than one species, please specify"
348
+ )
266
349
  self._temperature = self._melting_temperature
267
350
  self._temperature_stop = self._melting_temperature
268
351
  if self.temperature_high == 0:
269
- self._temperature_high = 2*self._melting_temperature
352
+ self._temperature_high = 2 * self._melting_temperature
270
353
  else:
271
354
  self._temperature_high = self.temperature_high
272
355
 
@@ -275,83 +358,96 @@ class Calculation(BaseModel, title='Main input class'):
275
358
  self._temperature = temp[0]
276
359
  self._temperature_stop = temp[0]
277
360
  if self.temperature_high == 0:
278
- self._temperature_high = 2*temp[0]
361
+ self._temperature_high = 2 * temp[0]
279
362
  else:
280
363
  self._temperature_high = self.temperature_high
281
-
364
+
282
365
  elif np.shape(self.temperature) == (2,):
283
366
  temp = self.temperature
284
367
  self._temperature = temp[0]
285
368
  self._temperature_stop = temp[1]
286
369
  if self.temperature_high == 0:
287
- self._temperature_high = 2*temp[1]
370
+ self._temperature_high = 2 * temp[1]
288
371
  else:
289
372
  self._temperature_high = self.temperature_high
290
373
 
291
-
292
- #fix pair styles
293
- #two main lists
374
+ # fix pair styles
375
+ # two main lists
294
376
  # _pair_style_with_options, read in as it is from file
295
377
  # _pair_style_names, just the names of the pair styles
296
378
 
297
379
  _pair_style_names = []
298
-
380
+
299
381
  for ps in self.pair_style:
300
382
  ps_split = ps.split()
301
383
  _pair_style_names.append(ps_split[0])
302
384
 
303
-
304
- #only set if its None
385
+ # only set if its None
305
386
  self._pair_style_with_options = self.pair_style
306
- self._pair_style_names = _pair_style_names
307
-
308
- #now fix pair coeffs with path
387
+ self._pair_style_names = _pair_style_names
388
+
389
+ # now fix pair coeffs with path
309
390
  if self.fix_potential_path:
310
391
  self.pair_coeff = self.fix_paths(self.pair_coeff)
311
-
392
+
312
393
  if np.isscalar(self.n_switching_steps):
313
394
  self._n_sweep_steps = self.n_switching_steps
314
395
  self._n_switching_steps = self.n_switching_steps
315
396
  else:
316
397
  self._n_sweep_steps = self.n_switching_steps[1]
317
398
  self._n_switching_steps = self.n_switching_steps[0]
318
-
319
- #here we also prepare lattice dict
399
+
400
+ # here we also prepare lattice dict
320
401
  for count, element in enumerate(self.element):
321
402
  self._element_dict[element] = {}
322
- self._element_dict[element]['mass'] = self.mass[count]
323
- self._element_dict[element]['count'] = 0
324
- self._element_dict[element]['composition'] = 0.0
325
- self._element_dict[element]['atomic_number'] = mendeleev.element(element).atomic_number
326
-
327
- #generate temporary filename if needed
403
+ self._element_dict[element]["mass"] = self.mass[count]
404
+ self._element_dict[element]["count"] = 0
405
+ self._element_dict[element]["composition"] = 0.0
406
+ self._element_dict[element]["atomic_number"] = mendeleev.element(
407
+ element
408
+ ).atomic_number
409
+
410
+ # generate temporary filename if needed
328
411
  write_structure_file = False
412
+ rename_structure_file = False
329
413
 
330
414
  if self.lattice == "":
331
- #fetch from dict
415
+ # fetch from dict
332
416
  if len(self.element) > 1:
333
- raise ValueError("Cannot create lattice for more than one element, provide a lammps-data file explicitly")
417
+ raise ValueError(
418
+ "Cannot create lattice for more than one element, provide a lammps-data file explicitly"
419
+ )
334
420
  if self.element[0] in element_dict.keys():
335
- self.lattice = element_dict[self.element[0]]['structure']
336
- self.lattice_constant = element_dict[self.element[0]]['lattice_constant']
421
+ self.lattice = element_dict[self.element[0]]["structure"]
422
+ self.lattice_constant = element_dict[self.element[0]][
423
+ "lattice_constant"
424
+ ]
337
425
  else:
338
- raise ValueError("Could not find structure, please provide lattice and lattice_constant explicitely")
339
-
340
- if self.repeat == [1,1,1]:
341
- self.repeat = [5,5,5]
426
+ raise ValueError(
427
+ "Could not find structure, please provide lattice and lattice_constant explicitely"
428
+ )
342
429
 
343
- structure = _make_crystal(self.lattice.lower(),
430
+ if self.repeat == [1, 1, 1]:
431
+ self.repeat = [5, 5, 5]
432
+
433
+ structure = _make_crystal(
434
+ self.lattice.lower(),
344
435
  lattice_constant=self.lattice_constant,
345
436
  repetitions=self.repeat,
346
- element=self.element)
437
+ element=self.element,
438
+ )
347
439
  structure = structure.write.ase()
348
-
349
- #extract composition
350
- types, typecounts = np.unique(structure.get_chemical_symbols(), return_counts=True)
440
+
441
+ # extract composition
442
+ types, typecounts = np.unique(
443
+ structure.get_chemical_symbols(), return_counts=True
444
+ )
351
445
 
352
446
  for c, t in enumerate(types):
353
- self._element_dict[t]['count'] = typecounts[c]
354
- self._element_dict[t]['composition'] = typecounts[c]/np.sum(typecounts)
447
+ self._element_dict[t]["count"] = typecounts[c]
448
+ self._element_dict[t]["composition"] = typecounts[c] / np.sum(
449
+ typecounts
450
+ )
355
451
 
356
452
  self._natoms = len(structure)
357
453
  self._original_lattice = self.lattice.lower()
@@ -359,86 +455,138 @@ class Calculation(BaseModel, title='Main input class'):
359
455
 
360
456
  elif self.lattice.lower() in structure_dict.keys():
361
457
  if len(self.element) > 1:
362
- raise ValueError("Cannot create lattice for more than one element, provide a lammps-data file explicitly")
458
+ raise ValueError(
459
+ "Cannot create lattice for more than one element, provide a lammps-data file explicitly"
460
+ )
363
461
 
364
- #this is a valid structure
462
+ # this is a valid structure
365
463
  if self.lattice_constant == 0:
366
- #we try try to get lattice_constant
464
+ # we try try to get lattice_constant
367
465
  if self.element[0] in element_dict.keys():
368
- self.lattice_constant = element_dict[self.element[0]]['lattice_constant']
466
+ self.lattice_constant = element_dict[self.element[0]][
467
+ "lattice_constant"
468
+ ]
369
469
  else:
370
- raise ValueError('Please provide lattice_constant!')
371
- #now create lattice
372
- structure = _make_crystal(self.lattice.lower(),
470
+ raise ValueError("Please provide lattice_constant!")
471
+ # now create lattice
472
+ structure = _make_crystal(
473
+ self.lattice.lower(),
373
474
  lattice_constant=self.lattice_constant,
374
475
  repetitions=self.repeat,
375
- element=self.element)
476
+ element=self.element,
477
+ )
376
478
  structure = structure.write.ase()
377
-
378
- #extract composition
379
- types, typecounts = np.unique(structure.get_chemical_symbols(), return_counts=True)
380
479
 
381
- for c, t in enumerate(types):
382
- self._element_dict[t]['count'] = typecounts[c]
383
- self._element_dict[t]['composition'] = typecounts[c]/np.sum(typecounts)
480
+ # extract composition
481
+ types, typecounts = np.unique(
482
+ structure.get_chemical_symbols(), return_counts=True
483
+ )
384
484
 
385
- #concdict_counts = {str(t): typecounts[c] for c, t in enumerate(types)}
386
- #concdict_frac = {str(t): typecounts[c]/np.sum(typecounts) for c, t in enumerate(types)}
387
- #self._composition = concdict_frac
388
- #self._composition_counts = concdict_counts
485
+ for c, t in enumerate(types):
486
+ self._element_dict[t]["count"] = typecounts[c]
487
+ self._element_dict[t]["composition"] = typecounts[c] / np.sum(
488
+ typecounts
489
+ )
490
+
491
+ # concdict_counts = {str(t): typecounts[c] for c, t in enumerate(types)}
492
+ # concdict_frac = {str(t): typecounts[c]/np.sum(typecounts) for c, t in enumerate(types)}
493
+ # self._composition = concdict_frac
494
+ # self._composition_counts = concdict_counts
389
495
  self._natoms = len(structure)
390
496
  self._original_lattice = self.lattice.lower()
391
497
  write_structure_file = True
392
-
498
+
393
499
  else:
394
- #this is a file
500
+ # this is a file
395
501
  if not os.path.exists(self.lattice):
396
- raise ValueError(f'File {self.lattice} could not be found')
397
- if self.file_format == 'lammps-data':
398
- #create atomic numbers for proper reading
399
- Z_of_type = dict([(count+1, self._element_dict[element]['atomic_number']) for count, element in enumerate(self.element)])
400
- structure = read(self.lattice, format='lammps-data', style='atomic', Z_of_type=Z_of_type)
401
- #structure = System(aseobj, format='ase')
502
+ raise ValueError(f"File {self.lattice} could not be found")
503
+ if self.file_format == "lammps-data":
504
+ # create atomic numbers for proper reading
505
+ Z_of_type = dict(
506
+ [
507
+ (count + 1, self._element_dict[element]["atomic_number"])
508
+ for count, element in enumerate(self.element)
509
+ ]
510
+ )
511
+ structure = read(
512
+ self.lattice,
513
+ format="lammps-data",
514
+ style="atomic",
515
+ Z_of_type=Z_of_type,
516
+ )
517
+ # structure = System(aseobj, format='ase')
518
+ rename_structure_file = True
402
519
  else:
403
- raise TypeError('Only lammps-data files are supported!')
520
+ raise TypeError("Only lammps-data files are supported!")
404
521
 
405
- #extract composition
406
- #this is the types read in from the file
407
- types, typecounts = np.unique(structure.get_chemical_symbols(), return_counts=True)
522
+ # extract composition
523
+ # this is the types read in from the file
524
+ types, typecounts = np.unique(
525
+ structure.get_chemical_symbols(), return_counts=True
526
+ )
408
527
  for c, t in enumerate(types):
409
- self._element_dict[t]['count'] = typecounts[c]
410
- self._element_dict[t]['composition'] = typecounts[c]/np.sum(typecounts)
411
-
528
+ self._element_dict[t]["count"] = typecounts[c]
529
+ self._element_dict[t]["composition"] = typecounts[c] / np.sum(
530
+ typecounts
531
+ )
532
+
412
533
  self._natoms = len(structure)
413
534
  self._original_lattice = os.path.basename(self.lattice)
414
535
  self.lattice = os.path.abspath(self.lattice)
415
536
 
416
- #if needed, write the file out
537
+ # if needed, write the file out
417
538
  if write_structure_file:
418
- structure_filename = ".".join([self.create_identifier(), str(self.kernel), "data"])
539
+ structure_filename = ".".join(
540
+ [self.create_identifier(), str(self.kernel), "data"]
541
+ )
542
+ structure_filename = os.path.join(os.getcwd(), structure_filename)
543
+ write(structure_filename, structure, format="lammps-data")
544
+ self.lattice = structure_filename
545
+
546
+ if rename_structure_file:
547
+ structure_filename = ".".join(
548
+ [self.create_identifier(), str(self.kernel), "data"]
549
+ )
419
550
  structure_filename = os.path.join(os.getcwd(), structure_filename)
420
- write(structure_filename, structure, format='lammps-data')
551
+ shutil.copy(self.lattice, structure_filename)
421
552
  self.lattice = structure_filename
422
-
423
- if self.mode == 'composition_scaling':
424
- #we also should check if actual contents are present
553
+
554
+ if self.mode == "composition_scaling":
555
+ # we also should check if actual contents are present
425
556
  input_chem_comp = {}
426
557
  for key, val in self._element_dict.items():
427
- input_chem_comp[key] = val['count']
558
+ input_chem_comp[key] = val["count"]
428
559
  self.composition_scaling._input_chemical_composition = input_chem_comp
429
560
 
430
- #now we should check output chem comp and see there are no keys extra
561
+ # now we should check output chem comp and see there are no keys extra
431
562
  for key in self.composition_scaling.output_chemical_composition.keys():
432
- if key not in self.composition_scaling._input_chemical_composition.keys():
433
- raise ValueError('An element in output composition is not possible with the given potential')
434
-
435
- natoms1 = np.sum([val for key, val in self.composition_scaling._input_chemical_composition.items()])
436
- natoms2 = np.sum([val for key, val in self.composition_scaling.output_chemical_composition.items()])
437
- if not (natoms1==natoms2):
438
- raise ValueError(f"Input and output number of atoms are not conserved! Input {self.dict_to_string(self.input_chemical_composition)}, output {self.dict_to_string(self.output_chemical_composition)}, total atoms in structure {structure.natoms}")
563
+ if (
564
+ key
565
+ not in self.composition_scaling._input_chemical_composition.keys()
566
+ ):
567
+ raise ValueError(
568
+ "An element in output composition is not possible with the given potential"
569
+ )
570
+
571
+ natoms1 = np.sum(
572
+ [
573
+ val
574
+ for key, val in self.composition_scaling._input_chemical_composition.items()
575
+ ]
576
+ )
577
+ natoms2 = np.sum(
578
+ [
579
+ val
580
+ for key, val in self.composition_scaling.output_chemical_composition.items()
581
+ ]
582
+ )
583
+ if not (natoms1 == natoms2):
584
+ raise ValueError(
585
+ f"Input and output number of atoms are not conserved! Input {self.dict_to_string(self.input_chemical_composition)}, output {self.dict_to_string(self.output_chemical_composition)}, total atoms in structure {structure.natoms}"
586
+ )
439
587
  return self
440
588
 
441
- def fix_paths(self, potlist):
589
+ def fix_paths(self, potlist):
442
590
  """
443
591
  Fix paths for potential files to complete ones
444
592
  """
@@ -456,7 +604,7 @@ class Calculation(BaseModel, title='Main input class'):
456
604
  else:
457
605
  fixedpots.append(pot)
458
606
  return fixedpots
459
-
607
+
460
608
  def create_identifier(self):
461
609
  """
462
610
  Generate an identifier
@@ -471,21 +619,32 @@ class Calculation(BaseModel, title='Main input class'):
471
619
  identistring: string
472
620
  unique identification string
473
621
  """
474
- #lattice processed
622
+ # lattice processed
475
623
  prefix = self.mode
476
624
  ts = int(self._temperature)
477
625
  if self._pressure is None:
478
626
  ps = "None"
479
627
  else:
480
- ps = "%d"%(int(self._pressure))
628
+ ps = "%d" % (int(self._pressure))
481
629
  l = self._original_lattice
482
- l = l.split('/')
630
+ l = l.split("/")
483
631
  l = l[-1]
484
-
632
+
485
633
  if self.folder_prefix is None:
486
- identistring = "-".join([prefix, l.lower(), self.reference_phase, str(ts), str(ps)])
634
+ identistring = "-".join(
635
+ [prefix, l.lower(), self.reference_phase, str(ts), str(ps)]
636
+ )
487
637
  else:
488
- identistring = "-".join([self.folder_prefix, prefix, l.lower(), self.reference_phase, str(ts), str(ps)])
638
+ identistring = "-".join(
639
+ [
640
+ self.folder_prefix,
641
+ prefix,
642
+ l.lower(),
643
+ self.reference_phase,
644
+ str(ts),
645
+ str(ps),
646
+ ]
647
+ )
489
648
  return identistring
490
649
 
491
650
  def get_folder_name(self):
@@ -509,99 +668,142 @@ class Calculation(BaseModel, title='Main input class'):
509
668
  """
510
669
  simfolder = self.get_folder_name()
511
670
 
512
- #if folder exists, delete it -> then create
671
+ # if folder exists, delete it -> then create
513
672
  if os.path.exists(simfolder):
514
- raise ValueError(f'Simulation folder {simfolder} exists. Please remove and run again!')
515
-
673
+ raise ValueError(
674
+ f"Simulation folder {simfolder} exists. Please remove and run again!"
675
+ )
676
+
516
677
  os.mkdir(simfolder)
517
678
  return simfolder
518
-
679
+
519
680
  @property
520
681
  def savefile(self):
521
682
  simfolder = self.get_folder_name()
522
- return os.path.join(simfolder, 'job.npy')
683
+ return os.path.join(simfolder, "job.npy")
684
+
523
685
 
524
686
  def save_job(job):
525
- filename = os.path.join(job.simfolder, 'job.npy')
687
+ filename = os.path.join(job.simfolder, "job.npy")
526
688
  np.save(filename, job)
527
689
 
690
+
528
691
  def load_job(filename):
529
692
  job = np.load(filename, allow_pickle=True).flatten()[0]
530
693
  return job
531
694
 
695
+
532
696
  def read_inputfile(file):
533
697
  if not os.path.exists(file):
534
- raise FileNotFoundError(f'Input file {file} not found.')
698
+ raise FileNotFoundError(f"Input file {file} not found.")
535
699
 
536
- with open(file, 'r') as fin:
700
+ with open(file, "r") as fin:
537
701
  data = yaml.safe_load(fin)
538
702
 
539
- if 'element' in data.keys():
540
- #old format
703
+ if "element" in data.keys():
704
+ # old format
541
705
  outfile = _convert_legacy_inputfile(file)
542
706
  else:
543
707
  outfile = file
544
708
  calculations = _read_inputfile(outfile)
545
- return calculations
709
+ return calculations
710
+
546
711
 
547
712
  def _read_inputfile(file):
548
- with open(file, 'r') as fin:
713
+ with open(file, "r") as fin:
549
714
  data = yaml.safe_load(fin)
550
715
  calculations = []
551
- for count, calc in enumerate(data['calculations']):
552
- calc['kernel'] = count
553
- calc['inputfile'] = file
554
- if 'pressure' in calc.keys():
555
- calc['pressure'] = _to_none(calc['pressure'])
716
+ for count, calc in enumerate(tqdm(data["calculations"])):
717
+ calc["kernel"] = count
718
+ calc["inputfile"] = file
719
+ if "pressure" in calc.keys():
720
+ calc["pressure"] = _to_none(calc["pressure"])
556
721
  calculations.append(Calculation(**calc))
557
722
  return calculations
558
723
 
724
+
559
725
  def _convert_legacy_inputfile(file, return_calcs=False):
560
- with open(file, 'r') as fin:
726
+ with open(file, "r") as fin:
561
727
  data = yaml.safe_load(fin)
562
- if not 'element' in data.keys():
563
- #new format
564
- raise ValueError('Not old format, exiting..')
728
+ if not "element" in data.keys():
729
+ # new format
730
+ raise ValueError("Not old format, exiting..")
565
731
 
566
- #prepare combos
732
+ # prepare combos
567
733
  calculations = []
568
- for cc, ci in enumerate(data['calculations']):
734
+ for cc, ci in enumerate(data["calculations"]):
569
735
  mode = ci["mode"]
570
736
  if mode == "melting_temperature":
571
737
  calc = {}
572
- for key in ['md', 'queue', 'tolerance', 'melting_temperature', 'nose_hoover', 'berendsen', 'composition_scaling', 'temperature_high']:
738
+ for key in [
739
+ "md",
740
+ "queue",
741
+ "tolerance",
742
+ "melting_temperature",
743
+ "nose_hoover",
744
+ "berendsen",
745
+ "composition_scaling",
746
+ "temperature_high",
747
+ ]:
573
748
  if key in data.keys():
574
- calc[key] = copy.copy(data[key])
575
- for key in ['element', 'mass', 'script_mode', 'lammps_executable', 'mpi_executable']:
749
+ calc[key] = copy.copy(data[key])
750
+ for key in [
751
+ "element",
752
+ "mass",
753
+ "script_mode",
754
+ "lammps_executable",
755
+ "mpi_executable",
756
+ ]:
576
757
  if key in data.keys():
577
758
  calc[key] = data[key]
578
- for key in ["mode", "pair_style", "pair_coeff", "pair_style_options", "npt",
579
- "repeat", "n_equilibration_steps",
580
- "n_switching_steps", "n_print_steps", "n_iterations", "potential_file", "spring_constants",
581
- "melting_cycle", "equilibration_control", "folder_prefix", "temperature_high"]:
759
+ for key in [
760
+ "mode",
761
+ "pair_style",
762
+ "pair_coeff",
763
+ "pair_style_options",
764
+ "npt",
765
+ "repeat",
766
+ "n_equilibration_steps",
767
+ "n_switching_steps",
768
+ "n_print_steps",
769
+ "n_iterations",
770
+ "potential_file",
771
+ "spring_constants",
772
+ "melting_cycle",
773
+ "equilibration_control",
774
+ "folder_prefix",
775
+ "temperature_high",
776
+ ]:
582
777
  if key in ci.keys():
583
778
  calc[key] = ci[key]
584
779
 
585
- #calc['pressure'] = float(np.atleast_1d(ci["pressure"]) if "pressure" in ci.keys() else np.atleast_1d(0))
586
- #calc['temperature'] = float(np.atleast_1d(ci["temperature"]) if "temperature" in ci.keys() else np.atleast_1d(0))
587
- #calc['lattice'] = str(ci["lattice"]) if "lattice" in ci.keys() else 'none'
588
- #calc['reference_phase'] = str(ci["reference_phase"]) if "reference_phase" in ci.keys() else 'none'
589
- #calc['lattice_constant'] = float(ci["lattice_constant"]) if "lattice_constant" in ci.keys() else 0
590
- calc['kernel'] = cc
591
- calc['inputfile'] = file
780
+ # calc['pressure'] = float(np.atleast_1d(ci["pressure"]) if "pressure" in ci.keys() else np.atleast_1d(0))
781
+ # calc['temperature'] = float(np.atleast_1d(ci["temperature"]) if "temperature" in ci.keys() else np.atleast_1d(0))
782
+ # calc['lattice'] = str(ci["lattice"]) if "lattice" in ci.keys() else 'none'
783
+ # calc['reference_phase'] = str(ci["reference_phase"]) if "reference_phase" in ci.keys() else 'none'
784
+ # calc['lattice_constant'] = float(ci["lattice_constant"]) if "lattice_constant" in ci.keys() else 0
785
+ calc["kernel"] = cc
786
+ calc["inputfile"] = file
592
787
  calculations.append(calc)
593
788
 
594
789
  else:
595
- pressure = np.atleast_1d(ci['pressure'])
596
- temperature = np.atleast_1d(ci['temperature'])
597
- lattice = np.atleast_1d(ci['lattice'])
598
- reference_phase = np.atleast_1d(ci['reference_phase'])
790
+ pressure = np.atleast_1d(ci["pressure"])
791
+ temperature = np.atleast_1d(ci["temperature"])
792
+ lattice = np.atleast_1d(ci["lattice"])
793
+ reference_phase = np.atleast_1d(ci["reference_phase"])
599
794
  if "lattice_constant" in ci.keys():
600
795
  lattice_constant = np.atleast_1d(ci["lattice_constant"])
601
796
  else:
602
797
  lattice_constant = [0 for x in range(len(lattice))]
603
798
 
604
- lat_props = [{"lattice": lattice[x], "lattice_constant":lattice_constant[x], "reference_phase":reference_phase[x]} for x in range(len(lattice))]
799
+ lat_props = [
800
+ {
801
+ "lattice": lattice[x],
802
+ "lattice_constant": lattice_constant[x],
803
+ "reference_phase": reference_phase[x],
804
+ }
805
+ for x in range(len(lattice))
806
+ ]
605
807
 
606
808
  if (mode == "fe") or (mode == "alchemy") or (mode == "composition_scaling"):
607
809
  combos = itertools.product(lat_props, pressure, temperature)
@@ -619,26 +821,55 @@ def _convert_legacy_inputfile(file, return_calcs=False):
619
821
  cc = 0
620
822
  for combo in combos:
621
823
  calc = {}
622
- for key in ['md', 'queue', 'tolerance', 'melting_temperature', 'nose_hoover', 'berendsen', 'composition_scaling', 'temperature_high']:
824
+ for key in [
825
+ "md",
826
+ "queue",
827
+ "tolerance",
828
+ "melting_temperature",
829
+ "nose_hoover",
830
+ "berendsen",
831
+ "composition_scaling",
832
+ "temperature_high",
833
+ ]:
623
834
  if key in data.keys():
624
- calc[key] = copy.copy(data[key])
625
- for key in ['element', 'mass', 'script_mode', 'lammps_executable', 'mpi_executable']:
835
+ calc[key] = copy.copy(data[key])
836
+ for key in [
837
+ "element",
838
+ "mass",
839
+ "script_mode",
840
+ "lammps_executable",
841
+ "mpi_executable",
842
+ ]:
626
843
  if key in data.keys():
627
844
  calc[key] = data[key]
628
- for key in ["mode", "pair_style", "pair_coeff", "pair_style_options", "npt",
629
- "repeat", "n_equilibration_steps",
630
- "n_switching_steps", "n_print_steps", "n_iterations", "potential_file", "spring_constants",
631
- "melting_cycle", "equilibration_control", "folder_prefix", "temperature_high"]:
845
+ for key in [
846
+ "mode",
847
+ "pair_style",
848
+ "pair_coeff",
849
+ "pair_style_options",
850
+ "npt",
851
+ "repeat",
852
+ "n_equilibration_steps",
853
+ "n_switching_steps",
854
+ "n_print_steps",
855
+ "n_iterations",
856
+ "potential_file",
857
+ "spring_constants",
858
+ "melting_cycle",
859
+ "equilibration_control",
860
+ "folder_prefix",
861
+ "temperature_high",
862
+ ]:
632
863
  if key in ci.keys():
633
864
  calc[key] = ci[key]
634
- #print(combo)
865
+ # print(combo)
635
866
  calc["lattice"] = str(combo[0]["lattice"])
636
867
  calc["lattice_constant"] = float(combo[0]["lattice_constant"])
637
868
  calc["reference_phase"] = str(combo[0]["reference_phase"])
638
869
  calc["pressure"] = _to_float(combo[1])
639
870
  calc["temperature"] = _to_float(combo[2])
640
- calc['kernel'] = cc
641
- calc['inputfile'] = file
871
+ calc["kernel"] = cc
872
+ calc["inputfile"] = file
642
873
  cc += 1
643
874
  calculations.append(calc)
644
875
 
@@ -646,11 +877,13 @@ def _convert_legacy_inputfile(file, return_calcs=False):
646
877
  return calculations
647
878
  else:
648
879
  newdata = {}
649
- newdata['calculations'] = calculations
650
- #print(newdata)
651
- outfile = 'new.'+file
652
- warnings.warn(f'Old style input file calphy < v2 found. Converted input in {outfile}. Please check!')
653
- with open(outfile, 'w') as fout:
880
+ newdata["calculations"] = calculations
881
+ # print(newdata)
882
+ outfile = "new." + file
883
+ warnings.warn(
884
+ f"Old style input file calphy < v2 found. Converted input in {outfile}. Please check!"
885
+ )
886
+ with open(outfile, "w") as fout:
654
887
  yaml.safe_dump(newdata, fout)
655
888
  return outfile
656
889