Python API Reference¶
The public API of amorphouspy is defined by what is importable directly from the top-level package:
The rest of the package is considered internal implementation and can be subject to breaking changes even in minor releases.
amorphouspy
¶
amorphouspy - workflows for atomistic modeling of oxide glasses.
Functions¶
analyze_structure(atoms: Atoms) -> StructureData
¶
Perform a comprehensive structural analysis of an atomic configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
Atoms
|
Atomic configuration to analyze. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
StructureData |
StructureData
|
Object containing structured analysis results. |
Example
structure_data = analyze_structure(my_atoms) print(structure_data.density)
Source code in amorphouspy/src/amorphouspy/workflows/structural_analysis.py
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 | |
check_neutral_oxide(oxide: str) -> None
¶
Check if an oxide formula is charge neutral based on standard oxidation states.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
oxide
|
str
|
The chemical formula of the oxide (e.g., "Al2O3"). |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the oxide is invalid, oxidation states cannot be determined, or the net charge is not zero. |
Source code in amorphouspy/src/amorphouspy/structure/composition.py
classify_oxygens(structure: Atoms, cutoff: float, former_types: list[int], o_type: int) -> dict[int, str]
¶
Classify each oxygen atom as bridging (BO), non-bridging (NBO), free, or triclustered.
Convenience wrapper around :func:compute_qn_and_classify that returns
only the oxygen classification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
The atomic structure as ASE object. |
required |
cutoff
|
float
|
Cutoff radius for former-O neighbor search (Å). |
required |
former_types
|
list[int]
|
Atom types (atomic numbers) considered as formers. |
required |
o_type
|
int
|
Atom type (atomic number) considered as oxygen. |
required |
Returns:
| Type | Description |
|---|---|
dict[int, str]
|
A mapping from real atom ID to oxygen class string: |
dict[int, str]
|
|
dict[int, str]
|
|
Example
o_classes = classify_oxygens(atoms, cutoff=2.0, former_types=[14], o_type=8)
Source code in amorphouspy/src/amorphouspy/analysis/qn_network_connectivity.py
compute_angles(structure: Atoms, center_type: int, neighbor_type: int, cutoff: float, bins: int = 180) -> tuple[np.ndarray, np.ndarray]
¶
Compute bond angle distribution between triplets of neighbor_type-center-neighbor_type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
Atomic structure. |
required |
center_type
|
int
|
Atom type at the angle center. |
required |
neighbor_type
|
int
|
Atom type forming the angle with center. |
required |
cutoff
|
float
|
Neighbor search cutoff. |
required |
bins
|
int
|
Number of histogram bins. Defaults to 180. |
180
|
Returns:
| Type | Description |
|---|---|
tuple[ndarray, ndarray]
|
A tuple containing: bin_centers: Bin centers (degrees). angle_hist: Normalized angle histogram. |
Example
bins, hist = compute_angles(structure, center_type=1, neighbor_type=2, cutoff=3.0)
Source code in amorphouspy/src/amorphouspy/analysis/bond_angle_distribution.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
compute_cavities(structure: Atoms, resolution: int = 64, cutoff_radii: dict[str, float] | float | None = None) -> dict[str, np.ndarray]
¶
Compute cavity distribution and shape properties for a glass structure.
Replicates sovapy's domain-based cavity detection algorithm. The simulation box is discretised into a 3D voxel grid; voxels within the discrete (integer-rounded) atomic radius of any atom are marked as occupied. Connected regions of empty voxels -- detected with full 26-connectivity and periodic-boundary-condition awareness -- constitute cavities (domains).
Atomic radii are rounded to the nearest integer number of voxels before
marking, matching sovapy's discrete_radius = round(radius / s_step)
scheme. This produces higher occupancy than using raw float radii and
is the key factor that reproduces sovapy's cavity count and volumes.
Periodic boundary conditions are handled via a union-find merge of cavity labels that connect across opposite box faces.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
ASE Atoms object with atomic coordinates and cell. |
required |
resolution
|
int
|
Number of voxels along the longest box dimension before padding (default 64). Higher values give more accurate surface areas and shapes at the cost of memory and runtime. |
64
|
cutoff_radii
|
dict[str, float] | float | None
|
Atomic radius specification. Three forms are accepted:
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, ndarray]
|
Dictionary with the following keys, each mapping to a 1D float64 |
dict[str, ndarray]
|
array with one entry per detected cavity: |
dict[str, ndarray]
|
|
dict[str, ndarray]
|
|
dict[str, ndarray]
|
|
dict[str, ndarray]
|
|
dict[str, ndarray]
|
|
Examples:
>>> from ase.io import read
>>> structure = read('glass.xyz')
>>> cavities = compute_cavities(structure, resolution=64)
>>> print(cavities['volumes'])
Source code in amorphouspy/src/amorphouspy/analysis/cavities.py
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | |
compute_coordination(structure: Atoms, target_type: int, cutoff: float, neighbor_types: list[int] | None = None) -> tuple[dict[int, int], dict[int, int]]
¶
Compute coordination number for atoms of a target type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
The atomic structure as ASE object. |
required |
target_type
|
int
|
Atom type (atomic number) for which to compute coordination. |
required |
cutoff
|
float
|
Cutoff radius in Å. |
required |
neighbor_types
|
list[int] | None
|
Valid neighbor atomic numbers. None means all types. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[dict[int, int], dict[int, int]]
|
A tuple containing: coordination_distribution: Mapping from coordination number to count. per_atom_coordination: Mapping from atom ID to coordination number. |
Example
structure = read('glass.xyz') dist, cn = compute_coordination(structure, target_type=14, cutoff=2.0)
Source code in amorphouspy/src/amorphouspy/analysis/radial_distribution_functions.py
compute_guttmann_rings(structure: Atoms, bond_lengths: dict[tuple[str, str], float], max_size: int = 24, n_cpus: int = 1) -> tuple[dict[int, int], float]
¶
Compute the Guttman ring size distribution and mean ring size.
Rings are detected using a native networkx-based BFS implementation of
Guttman's shortest-path (primitive) ring criterion. Only closed primitive
rings up to max_size former atoms are counted.
Ring size is defined as the number of network-former atoms (T atoms) in the ring, following Guttman's original convention.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
ASE Atoms object containing atomic coordinates and types. |
required |
bond_lengths
|
dict[tuple[str, str], float]
|
Maximum bond lengths for each element pair, e.g.
|
required |
max_size
|
int
|
Maximum ring size (number of T atoms) to search for. |
24
|
n_cpus
|
int
|
Number of worker processes for parallel ring search.
|
1
|
Returns:
| Name | Type | Description |
|---|---|---|
histogram |
dict[int, int]
|
Mapping from ring size to ring count. |
mean_ring_size |
float
|
Mean ring size weighted by count. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> from ase.io import read
>>> structure = read('glass.xyz')
>>> histogram, mean_size = compute_guttmann_rings(
... structure,
... bond_lengths={('Si', 'O'): 1.8},
... max_size=12,
... )
>>> # Parallel on 4 cores
>>> histogram, mean_size = compute_guttmann_rings(
... structure,
... bond_lengths={('Si', 'O'): 1.8},
... n_cpus=4,
... )
>>> # Parallel using all available cores
>>> histogram, mean_size = compute_guttmann_rings(
... structure,
... bond_lengths={('Si', 'O'): 1.8},
... n_cpus=-1,
... )
Source code in amorphouspy/src/amorphouspy/analysis/rings.py
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 | |
compute_network_connectivity(qn_dist: dict[int, int]) -> float
¶
Compute average network connectivity based on Qn distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qn_dist
|
dict[int, int]
|
Qn distribution histogram. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Average network connectivity. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If qn_dist is empty or sum of counts is zero. |
Example
qn_dist = {0: 10, 1: 50, 2: 100, 3: 200, 4: 50} connectivity = compute_network_connectivity(qn_dist)
Source code in amorphouspy/src/amorphouspy/analysis/qn_network_connectivity.py
compute_qn(structure: Atoms, cutoff: float, former_types: list[int], o_type: int) -> tuple[dict[int, int], dict[int, dict[int, int]]]
¶
Calculate Qn distribution.
The Q^n distribution characterizes connectivity of tetrahedral network-forming units based on bridging oxygens (BOs) count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
The atomic structure as ASE object. |
required |
cutoff
|
float
|
Cutoff radius for former-O neighbor search. |
required |
former_types
|
list[int]
|
Atom types considered as formers. |
required |
o_type
|
int
|
Atom type considered as oxygen. |
required |
Returns:
| Type | Description |
|---|---|
tuple[dict[int, int], dict[int, dict[int, int]]]
|
A tuple containing: total_qn: Total Qn distribution (mapping from n to count). partial_qn: Partial Qn (mapping from former type to Qn distribution). |
Example
structure = read('glass.xyz') total_qn, partial_qn = compute_qn( ... structure, cutoff=2.0, former_types=[14], o_type=8 ... )
Source code in amorphouspy/src/amorphouspy/analysis/qn_network_connectivity.py
compute_qn_and_classify(structure: Atoms, cutoff: float, former_types: list[int], o_type: int) -> tuple[dict[int, int], dict[int, dict[int, int]], dict[int, str]]
¶
Calculate Qn distribution and classify each oxygen as BO/NBO/free/tri.
Performs a single neighbour search pass to compute both the Q^n distribution and the per-oxygen classification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
The atomic structure as ASE object. |
required |
cutoff
|
float
|
Cutoff radius for former-O neighbor search (Å). |
required |
former_types
|
list[int]
|
Atom types (atomic numbers) considered as formers. |
required |
o_type
|
int
|
Atom type (atomic number) considered as oxygen. |
required |
Returns:
| Type | Description |
|---|---|
tuple[dict[int, int], dict[int, dict[int, int]], dict[int, str]]
|
A tuple containing:
total_qn: Total Qn distribution (mapping from n to count).
partial_qn: Partial Qn (mapping from former type to Qn distribution).
oxygen_classes: Mapping from real atom ID to oxygen class string
( |
Example
total_qn, partial_qn, o_classes = compute_qn_and_classify( ... structure, cutoff=2.0, former_types=[14], o_type=8 ... )
Source code in amorphouspy/src/amorphouspy/analysis/qn_network_connectivity.py
compute_rdf(structure: Atoms, r_max: float = 10.0, n_bins: int = 500, type_pairs: list[tuple[int, int]] | None = None) -> tuple[np.ndarray, dict, dict]
¶
Compute radial distribution functions (RDFs) and cumulative coordination numbers.
Calculates the pair-wise radial distribution function g(r) for specified atom-type pairs under periodic boundary conditions (orthogonal and triclinic), along with the cumulative coordination number n(r).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
ASE Atoms object. |
required |
r_max
|
float
|
Maximum distance in Å (default 10.0). |
10.0
|
n_bins
|
int
|
Number of radial bins (default 500). |
500
|
type_pairs
|
list[tuple[int, int]] | None
|
List of (atomic_number_1, atomic_number_2) pairs. None -> all unique unordered combinations of present types plus all same-type pairs. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
r |
ndarray
|
Radial bin centres in Å, shape (n_bins,). |
rdfs |
dict
|
Normalised g(r) for each type pair, shape (n_bins,). |
cn_cumulative |
dict
|
Mean number of neighbours of the second type within radius r around an atom of the first type, shape (n_bins,). |
Notes
- If r_max exceeds half the smallest perpendicular cell height, it is automatically reduced to the largest integer value that does not exceed that limit, and a warning message is printed.
- If the adjusted r_max would be zero or negative (cell too small), a ValueError is raised instead.
- This function operates on array indices internally (not atom IDs) because it only needs type information and distances, not ID lookup.
- Periodic boundaries are handled via the minimum-image convention in fractional space, so both orthogonal and triclinic cells are correct.
Example
structure = read('glass.xyz') r, rdfs, cn = compute_rdf(structure, r_max=10.0, n_bins=500) g_SiO = rdfs[(8, 14)] cn_SiO = cn[(8, 14)] # O around Si cn_OSi = cn[(14, 8)] # Si around O
Source code in amorphouspy/src/amorphouspy/analysis/radial_distribution_functions.py
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | |
compute_structure_factor(structure: Atoms, q_min: float = 0.5, q_max: float = 20.0, n_q: int = 500, r_max: float = 10.0, n_bins: int = 2000, radiation: str = 'neutron', *, lorch_damping: bool = True, type_pairs: list[tuple[int, int]] | None = None) -> tuple[np.ndarray, np.ndarray, dict[tuple[int, int], np.ndarray]]
¶
Compute the 1D isotropic structure factor S(q) and Faber-Ziman partials.
Uses the Faber-Ziman formalism to compute partial structure factors S_ab(q) from partial radial distribution functions g_ab(r) via the sine transform, then combines them into the total S(q) weighted by coherent neutron scattering lengths or q-dependent X-ray form factors.
Neutron diffraction (Faber-Ziman total structure factor):
S(q) = 1 + sum_{a<=b} (2 - d_ab) * c_a * c_b * b_a * b_b * [S_ab(q) - 1] / <b>^2
where c_a is the atomic concentration, b_a the coherent scattering length (from NIST), and = sum_a c_a * b_a.
X-ray diffraction: b_a is replaced by the q-dependent International Tables form factor f_a(q) (four-Gaussian fit via pymatgen), making the weights q-dependent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
ASE Atoms object with periodic boundary conditions. |
required |
q_min
|
float
|
Minimum momentum transfer in Angstroms^-1 (default 0.5). |
0.5
|
q_max
|
float
|
Maximum momentum transfer in Angstroms^-1 (default 20.0). |
20.0
|
n_q
|
int
|
Number of q-grid points (default 500). |
500
|
r_max
|
float
|
Real-space cutoff in Angstroms for the underlying RDF (default 10.0). |
10.0
|
n_bins
|
int
|
Number of radial bins for the RDF (default 2000). |
2000
|
radiation
|
str
|
Scattering probe: |
'neutron'
|
lorch_damping
|
bool
|
Apply the Lorch modification function M(r) = sinc(r/r_max) to suppress termination ripples from the finite r_max cutoff (default True). |
True
|
type_pairs
|
list[tuple[int, int]] | None
|
List of (Z_a, Z_b) pairs for which to compute partials.
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
q |
ndarray
|
Momentum transfer grid in Angstroms^-1, shape (n_q,). |
sq_total |
ndarray
|
Total structure factor S(q), shape (n_q,). |
sq_partials |
dict[tuple[int, int], ndarray]
|
Faber-Ziman partial structure factors S_ab(q), keyed by the canonical pair (min(Z_a, Z_b), max(Z_a, Z_b)), each with shape (n_q,). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
KeyError
|
If a scattering length or form factor is not available for an element present in the structure. |
Example
from ase.io import read structure = read("sodium_silicate.extxyz") q, sq, sq_partials = compute_structure_factor(structure, q_max=20.0) sq_SiO = sq_partials[(8, 14)] # partial S_SiO(q)
Source code in amorphouspy/src/amorphouspy/analysis/structure_factor.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | |
count_distribution(coord_numbers: dict[int, int]) -> dict[int, int]
¶
Convert coordination numbers to a histogram distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coord_numbers
|
dict[int, int]
|
Mapping from atom ID to coordination number. |
required |
Returns:
| Type | Description |
|---|---|
dict[int, int]
|
Coordination number frequency histogram. |
Example
dist = count_distribution({1: 4, 2: 4, 3: 3})
Source code in amorphouspy/src/amorphouspy/shared.py
create_random_atoms(composition: dict[str, float], n_molecules: int | None = None, target_atoms: int | None = None, mode: str = 'molar', stoichiometry: dict[str, dict[str, int]] | None = None, box_length: float = 50.0, min_distance: float = 1.6, seed: int = 42, max_attempts_per_atom: int = 100000) -> tuple[list[dict[str, str | list[float]]], dict[str, int]]
¶
Generate random atom positions for a glass system in a periodic cubic box.
Supports specifying system size either by total number of molecules (n_molecules)
or total number of atoms (target_atoms).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
composition
|
dict[str, float]
|
A dictionary mapping oxide formulas to their fractions, e.g. {"CaO": 0.25, "Al2O3": 0.25, "SiO2": 0.5}. |
required |
n_molecules
|
int | None
|
The desired total number of molecules. Mutually exclusive with |
None
|
target_atoms
|
int | None
|
The desired target number of atoms. Mutually exclusive with |
None
|
mode
|
str
|
The composition interpretation mode ("molar" or "weight"). Defaults to "molar". |
'molar'
|
stoichiometry
|
dict[str, dict[str, int]] | None
|
Optional pre-calculated stoichiometry dictionary. |
None
|
box_length
|
float
|
The length of the cubic simulation box in Angstroms. Defaults to 50.0. |
50.0
|
min_distance
|
float
|
The minimum allowed distance between any two atoms in Angstroms. Defaults to 1.6. |
1.6
|
seed
|
int
|
Random seed for reproducibility. Defaults to 42. |
42
|
max_attempts_per_atom
|
int
|
Maximum attempts to place a single atom before failing. Defaults to 100000. |
100000
|
Returns:
| Type | Description |
|---|---|
tuple[list[dict[str, str | list[float]]], dict[str, int]]
|
A tuple containing: - A list of atom dictionaries, each with "element" and "position". - A dictionary of total counts per element. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If composition sum is invalid or target mode is ambiguous. |
RuntimeError
|
If atom placement fails due to overcrowding (max attempts reached). |
Example
atoms_list, atom_counts = create_random_atoms( ... composition={"SiO2": 0.8, "Na2O": 0.2}, ... target_atoms=500, ... box_length=20.0 ... )
Source code in amorphouspy/src/amorphouspy/structure/geometry.py
cte_from_fluctuations_simulation(structure: Atoms, potential: str, temperature: float = 300, pressure: float = 0.0001, timestep: float = 1.0, equilibration_steps: int = 100000, production_steps: int = 200000, min_production_runs: int = 5, max_production_runs: int = 25, CTE_uncertainty_criterion: float = 1e-06, n_dump: int = 100000, n_log: int = 10, server_kwargs: dict[str, Any] | None = None, *, aniso: bool = False, seed: int | None = 12345, tmp_working_directory: str | Path | None = None) -> dict[str, Any]
¶
Perform a LAMMPS-based cte simulation protocol based on fluctuations.
This workflow equilibrates a structure at a target temperature and performs a production MD run to collect the instantaneous total energy, pressure and volume, needed to compute the CTE via H-V fluctuation analysis or V-T data, depending if only one temperature or multiple temperatures should be probed.
The number of steps used here is only for testing purposes. It is assumed in this workflow that the given in structure is pre-equilibrated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
Input structure (assumed pre-equilibrated). |
required |
potential
|
str
|
LAMMPS potential file. |
required |
temperature
|
float
|
Simulation temperature in Kelvin (default 300 K). |
300
|
pressure
|
float
|
Target pressure in GPa (use pyiron units here!) for NPT simulations (default: 10-4 GPa = 10^5 Pa = 1 bar). |
0.0001
|
timestep
|
float
|
MD integration timestep in femtoseconds (default 1.0 fs). |
1.0
|
equilibration_steps
|
int
|
Number of MD steps for the equilibration run (default 100,000). |
100000
|
production_steps
|
int
|
Number of MD steps for the production runs (default 200,000). |
200000
|
min_production_runs
|
int
|
Minimum number of production runs to perform before checking for convergence (default 2). |
5
|
max_production_runs
|
int
|
Maximum number of production runs to perform before checking for convergence (default 10). |
25
|
CTE_uncertainty_criterion
|
float
|
Convergence criterion for the uncertainty of the linear CTE (default 1e-6/K). |
1e-06
|
n_dump
|
int
|
Dump output frequency of the production runs (default 100,000). |
100000
|
n_log
|
int
|
Log output frequency (default 10). |
10
|
server_kwargs
|
dict[str, Any] | None
|
Additional server configuration arguments for pyiron. |
None
|
aniso
|
bool
|
If false, an isotropic NPT calculation is performed and the simulation box is scaled uniformly. If True, anisotropic NPT calculation is performed and the simulation box can change shape and size independently along each axis (default False). |
False
|
seed
|
int | None
|
Random seed for velocity initialization (default 12345). If None, a random seed is used. |
12345
|
tmp_working_directory
|
str | Path | None
|
Temporary directory for job execution. |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Nested dictionary containing the "summary" and "data" keys. In the "summary" section the CTE |
dict[str, Any]
|
values and their uncertainties are returned together with info whether CTE_uncertainty_criterion was |
dict[str, Any]
|
reached within the max_production_runs. |
dict[str, Any]
|
The "data" holds the collected data from all individual production runs. "run_index" is to |
dict[str, Any]
|
clearly identify which production run the data belongs to. "steps" contains the number of steps |
dict[str, Any]
|
for each run. Thermodynamic and structural data are averaged over each production run and these |
dict[str, Any]
|
averages are listed under the respective key. Finally, also the structure after the simulation |
dict[str, Any]
|
has finished is stored for further use or analysis. Example: |
dict[str, Any]
|
{ 'summary' : {"CTE_V_mean" : ..., "CTE_x_mean" : ..., "CTE_y_mean" : ..., "CTE_z_mean" : ..., "CTE_V_uncertainty" : ..., "CTE_x_uncertainty" : ..., "CTE_y_uncertainty" : ..., "CTE_z_uncertainty" : ..., "is_converged" : "True" or "False", "convergence_criterion" : float }, |
dict[str, Any]
|
'data': {"run_index" : [1, 2, 3, ...], "steps" : [...], "T" : [...], "E_tot" : [...], "ptot" : [...], "pxx" : [...], "pyy" : [...], "pzz" : [...], "V" : [...], "Lx" : [...], "Ly" : [...], "Lz" : [...], "CTE_V" : [...], "CTE_x" : [...], "CTE_y" : [...], "CTE_z" : [...], "structure_final" : Atoms } |
dict[str, Any]
|
} |
Notes
- The structure is first pre-equilibrated with a short, hard-coded 10 ps NVT run. Only then follows the user-defined NPT equilibration and production runs.
- The simulation is only marked as converged if the uncertainty criterion is reached for all four CTE values (CTE_V, CTE_x, CTE_y, CTE_z) at the same time. The CTE_uncertainty_criterion is applied to as-is to the linear CTEs. How this is treated for the CTE_V, see next point.
- How to chose the uncertainty criterion for the volumetric CTE? Should it be the same as the defined CTE_uncertainty_criterion applied to the linear CTEs? Consider this: The volumetric CTE is approximately the sum of the linear CTEs along x, y, and z. If three variables x, y and z were uncorrelated and have known uncertainties sigma_x, sigma_y, and sigma_z, the uncertainty of the sum of them would be: sigma_V = sqrt( sigma_x2 + sigma_y2 + sigma_z2 ). If we assume that the uncertainty criterion is reached at roughly the same time for all three variables, the uncertainty of the volumetric CTE can be approximated as sqrt(3)CTE_uncertainty_criterion. However, x, y and z are typically not uncorrelated. If calculated from the actual simulation data, the uncertainty of CTE_V is found to be approximately the same as the individual uncertainties of the linear CTEs. To be not too strict here, we keep the sqrt(3)CTE_uncertainty_criterion for CTE_V.
Example
result = cte_from_fluctuations_simulation( ... structure=my_atoms, ... potential=my_potential, ... temperature=300, ... equilibration_steps=500_000, ... production_steps=200_000, ... min_production_runs=10, ... max_production_runs=50, ... CTE_uncertainty_criterion=1e-6, ... )
Source code in amorphouspy/src/amorphouspy/workflows/cte.py
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | |
cte_from_npt_fluctuations(temperature: float | list | np.ndarray, enthalpy: list | np.ndarray, volume: list | np.ndarray, N_points: int = 1000, *, use_running_mean: bool = False) -> float
¶
Compute the CTE from enthalpy-volume cross-correlations.
Data needs to be obtained from a constant pressure, constant temperature NPT simulation. Formula used can be found in See Tildesley, Computer Simulation of Liquids, Eq. 2.94.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
temperature
|
float | list | ndarray
|
Target temperature (defining the ensemble) in K. Can be specified as a single value (int/float). If provided as list or array-like, the mean will be used as target temperature. |
required |
enthalpy
|
list | ndarray
|
"Instantaneous enthalpy" in eV, list or np.ndarray. Not that the instantaneous enthalpy is given by H = U + pV, where U is the instantaneous internal energy (i.e., kinetic + potential of every timestep), p the pressure defining the ensemble and V the average volume. Note that this is not the same quantity as the enthalpy obtained from LAMMPS directly via the 'enthalpy' output from thermo_modify. The latter uses the instantaneous pressure at the given timestep instead of the average (or ensemble-defining) pressure. |
required |
volume
|
list | ndarray
|
Instantaneous volume in Ang^3, list or np.ndarray. Here, one can also feed individual cell lengths for anisotropic CTE calculations. |
required |
N_points
|
int
|
Window size for running mean calculation if running_mean is True. Defaults to 1000. |
1000
|
use_running_mean
|
bool
|
Conventionally, fluctuations are calculated as difference from the mean of the whole trajectory. If use_running_mean is True, running mean values are used to determine fluctuations. This can be useful for non-stationary data where drift in volume and energy is still observed. Defaults to False. |
False
|
Returns:
| Type | Description |
|---|---|
float
|
The calculated CTE value in 1/K. |
Example
cte = cte_from_npt_fluctuations( ... temperature=300.0, ... enthalpy=enthalpy_array, ... volume=volume_array ... )
Notes
- If temperature is provided as int/float, it will be used as-is for the target temperature to compute the CTE.
- If temperature is provided as list/array-like, the mean of the temperature data will be used as target temperature for the CTE calculation. See below how this is slightly changed if running mean is used.
- If running_mean is set to True used, a part of the volume and enthalpy data at the beginning and end of the arrays cannot be used because it is required to calculate the running mean values. To ensure that the data for the calculation of the fluctuations is consistent with the mean volume and target temperature (in case this was provided as list/array-like), also this data will be trimmed correspondingly.
Source code in amorphouspy/src/amorphouspy/analysis/cte.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
cte_from_volume_temperature_data(temperature: list | np.ndarray, volume: list | np.ndarray, reference_volume: float | None = None) -> tuple[float, float]
¶
Compute the CTE from slope of a linear fit to volume-temperature data.
This can be done from by performing various constant pressure, constant temperature NPT simulation at different temperatures. Afterwards, collect the averaged volume and fit linearly to the temperature. Divide slope by the volume belonging to the lowest temperature to obtain the CTE. If specified, the volume-temperature plot is shown and the fitted line is overlaid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
temperature
|
list | ndarray
|
Temperature in K. |
required |
volume
|
list | ndarray
|
Volume in Angstrom^3 or other structural quantity of interest. E.g., for anisotropic CTE calculations, one can also use individual cell lengths in Angstroms here. |
required |
reference_volume
|
optional
|
Reference of the volume (or structural quantity of interest) the CTE will be calculated from. If not otherwise specified, the volume corresponding to the lowest temperature is used as reference. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
The calculated CTE value in 1/K and the R^2 value of the linear fit. |
Note
- This function is currently not in use in the workflows. But it might be used for cross-checking.
- If volume-temperature data is used, the CTE needs to be divided by 3 to obtain the linear CTE.
Example
cte = cte_from_volume_temperature_data( ... temperature=[300, 400, 500], ... volume=[100.0, 100.1, 100.2] ... )
Source code in amorphouspy/src/amorphouspy/analysis/cte.py
elastic_simulation(structure: Atoms, potential: pd.DataFrame, temperature_sim: float = 300.0, pressure: float | None = None, timestep: float = 1.0, equilibration_steps: int = 1000000, production_steps: int = 10000, n_print: int = 1, strain: float = 0.001, server_kwargs: dict[str, Any] | None = None, *, langevin: bool = False, seed: int = 12345, tmp_working_directory: str | Path | None = None) -> dict[str, Any]
¶
Perform a LAMMPS-based elastic constant simulation via the stress-strain method.
This workflow calculates the elastic stiffness tensor (Cij) using finite differences. It equilibrates the structure, applies small normal and shear strains, and measures the resulting stress response to determine the elastic constants.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
Input structure (assumed pre-equilibrated). |
required |
potential
|
DataFrame
|
LAMMPS potential file. |
required |
temperature_sim
|
float
|
Simulation temperature in Kelvin (default 5000.0 K). |
300.0
|
pressure
|
float | None
|
Target pressure for equilibration (default None, i.e., NVT). |
None
|
timestep
|
float
|
MD integration timestep in femtoseconds (default 1.0 fs). |
1.0
|
equilibration_steps
|
int
|
Number of steps for the initial equilibration phase (default 1,000,000). |
1000000
|
production_steps
|
int
|
Number of MD steps for the production run (default 10,000). |
10000
|
n_print
|
int
|
Thermodynamic output frequency (default 1). |
1
|
strain
|
float
|
Magnitude of the strain applied for finite differences (default 1e-3). |
0.001
|
server_kwargs
|
dict[str, Any] | None
|
Additional server configuration arguments. |
None
|
langevin
|
bool
|
Whether to use Langevin dynamics (default False). |
False
|
seed
|
int
|
Random seed for velocity initialization (default 12345). |
12345
|
tmp_working_directory
|
str | Path | None
|
Temporary directory for job execution. |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary containing the results. Key "result" contains the "Cij" 6x6 matrix. |
Notes
- The structure is first equilibrated (NPT/NVT).
- Positive and negative strains are applied to cancel lower-order errors (central difference).
- Calculated Cij values assume Voigt notation.
- For production simulations, system size, cooling rate, equilibration time, and strain magnitude should be tested to ensure the robustness of the results.
Example
result = elastic_simulation( ... structure=my_atoms, ... potential=my_potential, ... temperature_sim=300.0, ... strain=0.001 ... )
Source code in amorphouspy/src/amorphouspy/workflows/elastic_mod.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
extract_composition(composition: dict[str, float], tolerance: float = COMPOSITION_TOLERANCE) -> dict[str, float]
¶
Validate and normalize a composition dictionary.
Handles both fractional (0.0-1.0) and percentage (0-100) inputs. Always returns fractions summing to 1.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
composition
|
dict[str, float]
|
A dictionary mapping oxide formulas to their fractions, e.g. {"CaO": 0.25, "Al2O3": 0.25, "SiO2": 0.5}. |
required |
tolerance
|
float
|
Maximum allowed fractional deviation from 1.0. Defaults to
|
COMPOSITION_TOLERANCE
|
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dictionary mapping oxide formulas to their molar fractions. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the composition is empty, contains invalid elements, non-neutral oxides, or sums to an invalid total. |
Source code in amorphouspy/src/amorphouspy/structure/composition.py
find_rdf_minimum(r: np.ndarray, rdf_data: np.ndarray, sigma: float = 2.0, window_length: int = 21, polyorder: int = 3) -> float | None
¶
Identify the first minimum in a radial distribution function (RDF) curve.
The function applies Gaussian and Savitzky-Golay smoothing to reduce noise, detects the first peak, and searches for the first subsequent minimum using gradient sign changes. If no valid minimum is found, it falls back to detecting where the RDF drops below 1.0 after the peak.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
r
|
ndarray
|
Array of radial distances (Å), assumed to be uniformly spaced. |
required |
rdf_data
|
ndarray
|
RDF values corresponding to |
required |
sigma
|
float
|
Standard deviation for Gaussian smoothing. |
2.0
|
window_length
|
int
|
Window length for Savitzky-Golay filter. Must be odd. |
21
|
polyorder
|
int
|
Polynomial order for Savitzky-Golay filter. |
3
|
Returns:
| Type | Description |
|---|---|
float | None
|
The radial distance of the first minimum (Å) if found, otherwise |
Example
r = np.linspace(0, 10, 100) rdf = np.sin(r) + 1.0 # Dummy data min_r = find_rdf_minimum(r, rdf)
Source code in amorphouspy/src/amorphouspy/workflows/structural_analysis.py
fit_vft(T_data: ArrayLike, log10_eta_data: ArrayLike, initial_guess: tuple[float, float, float] = (-3, 1000, 200)) -> tuple[tuple[float, float, float], NDArray[np.float64]]
¶
Fits viscosity data to the Vogel-Fulcher-Tammann (VFT) model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
T_data
|
ArrayLike
|
Temperatures in Kelvin. |
required |
log10_eta_data
|
ArrayLike
|
log10(viscosity) values. |
required |
initial_guess
|
tuple[float, float, float]
|
Initial guess for (log10_eta0, B, T0). |
(-3, 1000, 200)
|
Returns:
| Type | Description |
|---|---|
tuple[tuple[float, float, float], NDArray[float64]]
|
A tuple containing the best-fit parameters and the covariance matrix. |
Example
params, cov = fit_vft(temps, log_visc_data) log10_eta0, B, T0 = params
Source code in amorphouspy/src/amorphouspy/workflows/viscosity.py
formula_mass_g_per_mol(formula: str) -> float
¶
Calculate the molar mass of a compound using ASE atomic masses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
A string representing the chemical formula (e.g., "SiO2"). |
required |
Returns:
| Type | Description |
|---|---|
float
|
The molar mass of the compound in grams per mole. |
Source code in amorphouspy/src/amorphouspy/structure/composition.py
generate_bond_length_dict(atoms: Atoms, specific_cutoffs: dict[tuple[str, str], float] | None = None, default_cutoff: float = -1.0) -> dict[tuple[str, str], float]
¶
Generate all symmetric element pairs and assign bond length cutoffs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
Atoms
|
ASE Atoms object whose species determine the pair set. |
required |
specific_cutoffs
|
dict[tuple[str, str], float] | None
|
Optional cutoff overrides for specific element
pairs. Both orderings |
None
|
default_cutoff
|
float
|
Fallback bond length for pairs not in
|
-1.0
|
Returns:
| Type | Description |
|---|---|
dict[tuple[str, str], float]
|
Dictionary mapping every symmetric element pair to its cutoff. |
Examples:
>>> from ase.io import read
>>> structure = read('glass.xyz')
>>> bond_lengths = generate_bond_length_dict(
... structure,
... specific_cutoffs={('Si', 'O'): 1.8},
... default_cutoff=2.0,
... )
Source code in amorphouspy/src/amorphouspy/analysis/rings.py
generate_potential(atoms_dict: dict, potential_type: str = 'pmmcs', *, melt: bool = True) -> pd.DataFrame
¶
Generate LAMMPS potential configuration for glass simulations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms_dict
|
dict
|
Dictionary containing atomic structure information. |
required |
potential_type
|
str
|
Type of potential to generate. Options are "pmmcs", "bjp", or "shik". (default is "pmmcs"). |
'pmmcs'
|
melt
|
bool
|
Append a Langevin + NVE/limit melt run block (10000 steps). Only used when
|
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame containing potential configuration. |
Example
potential = generate_potential(struct_dict, potential_type="shik") potential = generate_potential(struct_dict, potential_type="shik", melt=False)
Source code in amorphouspy/src/amorphouspy/potentials/potential.py
get_ase_structure(atoms_dict: dict, replicate: tuple[int, int, int] = (1, 1, 1)) -> Atoms
¶
Generate a LAMMPS data file format string and read into an ASE Atoms object.
Based on the specifications in the provided atoms_dict, this function generates a LAMMPS data file format string, which is then read into an ASE Atoms object. The ASE Atoms object is then returned. atoms_dict is expected to specify a cubic box. Triclinic boxes are not supported.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms_dict
|
dict
|
Dictionary that must contain the atom counts and box dimensions under the keys "atoms" and "box". |
required |
replicate
|
tuple[int, int, int]
|
Replication factors for the box in x, y, and z directions. Default is (1, 1, 1), meaning no replication. |
(1, 1, 1)
|
Returns:
| Type | Description |
|---|---|
Atoms
|
ASE Atoms object of the specified structure. |
Example
struct_dict = get_structure_dict({"CaO": 0.25, "Al2O3": 0.25, "SiO2": 0.5}, target_atoms=1000) atoms = get_ase_structure(struct_dict)
Source code in amorphouspy/src/amorphouspy/structure/geometry.py
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | |
get_atomic_mass(element: str | int) -> float
¶
Get the atomic mass of an element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
str | int
|
Chemical symbol or atomic number. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Atomic mass in g/mol. |
Example
mass = get_atomic_mass("Si") print(mass) 28.085
Source code in amorphouspy/src/amorphouspy/mass.py
get_composition(composition: dict[str, float], mode: str = 'molar') -> dict[str, float]
¶
Convert a composition dictionary into normalized molar fractions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
composition
|
dict[str, float]
|
A dictionary mapping oxide formulas to their fractions, e.g. {"CaO": 0.25, "Al2O3": 0.25, "SiO2": 0.5}. |
required |
mode
|
str
|
The interpretation mode, either 'molar' or 'weight'. Defaults to "molar". |
'molar'
|
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dictionary mapping oxide formulas to their molar fractions. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in amorphouspy/src/amorphouspy/structure/composition.py
get_glass_density_from_model(composition: dict[str, float]) -> float
¶
Calculate the room-temperature glass density using Fluegel's empirical model.
The model uses a polynomial expansion based on mole percentages of oxides. Source: Fluegel, A. "Global Model for Calculating Room-Temperature Glass Density from the Composition", J. Am. Ceram. Soc., 90 [8] 2622-2635 (2007).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
composition
|
dict[str, float]
|
A dictionary mapping oxide formulas to their fractions. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The calculated density in g/cm^3. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the composition contains components unsupported by the model or if the format is invalid. |
Example
density = get_glass_density_from_model({"SiO2": 0.75, "Na2O": 0.25})
Source code in amorphouspy/src/amorphouspy/structure/density.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | |
get_neighbors(atoms: Atoms | tuple[np.ndarray, np.ndarray, np.ndarray], cutoff: float | dict[tuple[int, int], float], target_types: list[int] | None = None, neighbor_types: list[int] | None = None, *, return_vectors: bool = False, use_numba: bool | None = None) -> list[tuple]
¶
Find all neighbors within cutoff for each atom.
Returns a list of tuples where all IDs are the real atom IDs from the structure file (e.g. non-sequential LAMMPS/XYZ ids), not array indices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
Atoms | tuple[ndarray, ndarray, ndarray]
|
Either an ASE Atoms object or a tuple (coords, types, cell_matrix) where cell_matrix is a (3,3) array with lattice vectors as rows. |
required |
cutoff
|
float | dict[tuple[int, int], float]
|
Cutoff radius in Angstrom. Either: - A single float applied uniformly to all pairs. - A dict mapping (atomic_number_i, atomic_number_j) to a pair-specific cutoff in Angstrom. Symmetric: (8, 14) and (14, 8) are equivalent. Pairs not listed default to the maximum cutoff in the dict. The cell list is built on the maximum cutoff so only one build is needed. |
required |
target_types
|
list[int] | None
|
Atomic numbers of atoms to find neighbors for. None means all atoms. |
None
|
neighbor_types
|
list[int] | None
|
Atomic numbers that count as valid neighbors. None means all types. |
None
|
return_vectors
|
bool
|
If True, each output tuple gains a third element — a (k, 3) float64 array of Cartesian minimum-image bond vectors (i -> j) in Angstrom. Scalar distances are np.linalg.norm(vectors, axis=1). |
False
|
use_numba
|
bool | None
|
Force Numba on/off. None = auto-detect. |
None
|
Returns:
| Type | Description |
|---|---|
list[tuple]
|
If return_vectors=False (default): [(central_id, [neighbor_ids]), ...] |
list[tuple]
|
If return_vectors=True: [(central_id, [neighbor_ids], vectors_shape_k3), ...] |
Examples:
Scalar cutoff (backward compatible)::
>>> neighbors = get_neighbors(atoms, cutoff=3.5)
>>> for central_id, nn_ids in neighbors:
... print(central_id, nn_ids)
Per-pair cutoffs for a Na2O-Al2O3-SiO2 glass::
>>> cutoff = {(14, 8): 2.0, (13, 8): 1.9, (11, 8): 2.7}
>>> neighbors = get_neighbors(atoms, cutoff=cutoff)
With bond vectors for Steinhardt parameters::
>>> result = get_neighbors(atoms, cutoff=3.5, return_vectors=True)
>>> for central_id, nn_ids, vecs in result:
... distances = np.linalg.norm(vecs, axis=1)
... print(f"atom {central_id}: mean bond length = {distances.mean():.3f} A")
Quick lookup by original atom ID::
>>> nl = {cid: nn for cid, nn, *_ in get_neighbors(atoms, cutoff=3.5)}
>>> nl[43586]
Source code in amorphouspy/src/amorphouspy/neighbors.py
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 | |
get_structure_dict(composition: dict[str, float], n_molecules: int | None = None, target_atoms: int | None = None, mode: str = 'molar', density: float | None = None, min_distance: float = 1.6, max_attempts_per_atom: int = 10000) -> dict
¶
Generate a structure dictionary for a given composition.
Supports both n_molecules and target_atoms input modes, and both molar and weight composition modes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
composition
|
dict[str, float]
|
A dictionary mapping oxide formulas to their fractions, e.g. {"CaO": 0.25, "Al2O3": 0.25, "SiO2": 0.5}. |
required |
n_molecules
|
int | None
|
Total number of molecules (traditional mode). |
None
|
target_atoms
|
int | None
|
Target number of atoms (new mode). |
None
|
mode
|
str
|
Composition mode: "molar" for mol%, "weight" for weight%. |
'molar'
|
density
|
float | None
|
Density in g/cm^3, default is calculated from model. |
None
|
min_distance
|
float
|
Minimum distance between any two atoms in angstroms, default is 1.6 Å. |
1.6
|
max_attempts_per_atom
|
int
|
Maximum attempts to place an atom before giving up, default is 10000. |
10000
|
Returns:
| Type | Description |
|---|---|
dict
|
A dictionary containing: - "atoms": A list of atom dictionaries with keys "element" and "position" - "box": The length of the cubic box in angstroms - "formula_units": Dictionary of oxide formula units - "total_atoms": Total number of atoms - "element_counts": Dictionary of element counts (if target_atoms mode) - "mol_fraction": Dictionary of molar fractions (if target_atoms mode) |
Example
struct_dict = get_structure_dict( ... composition={"CaO": 0.25, "Al2O3": 0.25, "SiO2": 0.5}, ... target_atoms=1000, ... mode="molar" ... )
Source code in amorphouspy/src/amorphouspy/structure/geometry.py
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | |
get_viscosity(result: dict[str, Any], timestep: float = 1.0, output_frequency: int = 1, max_lag: int | None = 1000000, cutoff_method: str = 'noise_threshold') -> dict[str, float]
¶
Compute viscosity using the Green-Kubo formalism from MD stress data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
dict[str, Any]
|
Parsed output dictionary from |
required |
timestep
|
float
|
MD integration time step in femtoseconds. |
1.0
|
output_frequency
|
int
|
Number of MD steps between stored frames in the output. |
1
|
max_lag
|
int | None
|
Maximum correlation lag (number of steps). Defaults to 1,000,000. |
1000000
|
cutoff_method
|
str
|
Method passed to |
'noise_threshold'
|
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dictionary containing: - temperature: Mean simulation temperature (K) - viscosity: Computed viscosity (Pa·s) - max_lag: Cutoff time per stress component (ps) - lag_time_ps: Array of lag times in picoseconds - sacf: Averaged normalized stress autocorrelation function - viscosity_integral: Cumulative viscosity integral (Pa·s) vs lag time |
Example
result_dict = get_viscosity(simulation_output, timestep=1.0, output_frequency=1, max_lag=1000000) print(result_dict['viscosity']) print(result_dict['sacf']) print(result_dict['viscosity_integral'])
Source code in amorphouspy/src/amorphouspy/workflows/viscosity.py
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 | |
load_lammps_dump(path: str | Path, type_map: dict[int, str] | None = None, *, frame: int | None = None, start: int | None = None, stop: int | None = None, step: int | None = None, return_atoms_dict: bool = False) -> Atoms | list[Atoms] | tuple[Atoms, dict[str, Any]] | list[tuple[Atoms, dict[str, Any]]]
¶
Read a LAMMPS dump file and return ASE Atoms object(s) with correct chemical symbols.
By default the full trajectory is returned as a list. Pass frame to get a single frame, or start / stop / step to slice the trajectory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to the LAMMPS dump file. |
required |
type_map
|
dict[int, str] | None
|
Mapping from LAMMPS integer type to element symbol,
e.g. |
None
|
frame
|
int | None
|
Zero-based index of a single frame to read. Mutually exclusive with start / stop / step. |
None
|
start
|
int | None
|
First frame index of the slice (inclusive, default 0). |
None
|
stop
|
int | None
|
Last frame index of the slice (exclusive, default end of file). |
None
|
step
|
int | None
|
Stride between selected frames (default 1). |
None
|
return_atoms_dict
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
Atoms | list[Atoms] | tuple[Atoms, dict[str, Any]] | list[tuple[Atoms, dict[str, Any]]]
|
|
Atoms | list[Atoms] | tuple[Atoms, dict[str, Any]] | list[tuple[Atoms, dict[str, Any]]]
|
|
Atoms | list[Atoms] | tuple[Atoms, dict[str, Any]] | list[tuple[Atoms, dict[str, Any]]]
|
|
Atoms | list[Atoms] | tuple[Atoms, dict[str, Any]] | list[tuple[Atoms, dict[str, Any]]]
|
|
Atoms | list[Atoms] | tuple[Atoms, dict[str, Any]] | list[tuple[Atoms, dict[str, Any]]]
|
Each |
Raises:
| Type | Description |
|---|---|
ValueError
|
If frame is combined with start / stop / step. |
ValueError
|
If type_map is |
Example
Full trajectory¶
frames = load_lammps_dump("run.lammpstrj", type_map={1: "O", 2: "Si"})
Single frame¶
ase_atoms = load_lammps_dump("run.lammpstrj", type_map={1: "O", 2: "Si"}, frame=0)
Frames 30-49¶
frames = load_lammps_dump("run.lammpstrj", type_map={1: "O", 2: "Si"}, start=30, stop=50)
Every 10th frame from 0 to 99¶
frames = load_lammps_dump("run.lammpstrj", type_map={1: "O", 2: "Si"}, start=0, stop=100, step=10)
Source code in amorphouspy/src/amorphouspy/io_utils.py
md_simulation(structure: Atoms, potential: pd.DataFrame, temperature_sim: float = 5000.0, timestep: float = 1.0, production_steps: int = 10000000, n_print: int = 1000, server_kwargs: dict | None = None, *, temperature_end: float | None = None, pressure: float | None = None, pressure_end: float | None = None, langevin: bool = False, seed: int = 12345, tmp_working_directory: str | Path | None = None) -> dict
¶
Perform a molecular dynamics simulation using LAMMPS.
This function equilibrates a structure at a predefined temperature and pressure, with optional linear ramps for temperature and/or pressure over the course of the simulation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
The initial atomic structure to be melted and quenched. |
required |
potential
|
DataFrame
|
The potential file to be used for the simulation. |
required |
temperature_sim
|
float
|
Start temperature in K (or constant temperature when |
5000.0
|
timestep
|
float
|
Time step for integration in femtoseconds (default is 1.0 fs). |
1.0
|
production_steps
|
int
|
The number of steps for the production. |
10000000
|
n_print
|
int
|
The frequency of output during the simulation (default is 1000). |
1000
|
server_kwargs
|
dict | None
|
Additional arguments for the server. |
None
|
temperature_end
|
float | None
|
End temperature in K for a linear ramp from |
None
|
pressure
|
float | None
|
Start pressure in GPa. If None, NVT ensemble is used.
Provide a value (e.g. |
None
|
pressure_end
|
float | None
|
End pressure in GPa for a linear pressure ramp. Requires |
None
|
langevin
|
bool
|
Whether to use Langevin dynamics. |
False
|
seed
|
int
|
Random seed for velocity initialization (default is 12345). Ignored if |
12345
|
tmp_working_directory
|
str | Path | None
|
The directory where the simulation files will be stored. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
A dictionary containing the simulation steps and temperature data. |
Source code in amorphouspy/src/amorphouspy/workflows/md.py
melt_quench_simulation(structure: Atoms, potential: pd.DataFrame, temperature_high: float | None = None, temperature_low: float = 300.0, timestep: float = 1.0, heating_rate: float = 1000000000000.0, cooling_rate: float = 1000000000000.0, n_print: int = 1000, equilibration_steps: int | None = None, *, server_kwargs: dict | None = None, langevin: bool = False, seed: int = 12345, tmp_working_directory: str | Path | None = None) -> dict
¶
Perform a melt-quench simulation using LAMMPS.
This function heats a structure to a high temperature, equilibrates it, and then cools it down to a low temperature, simulating a melt-quench process. The heating and cooling rates are given in K/s, and the conversion into simulation steps is done automatically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
The initial atomic structure to be melted and quenched. |
required |
potential
|
DataFrame
|
The potential file to be used for the simulation. |
required |
temperature_high
|
float | None
|
The high temperature to which the structure will be heated. If None, the protocol's default melt temperature is used (e.g. 4000 K for SHIK, 5000 K for others). |
None
|
temperature_low
|
float
|
The low temperature to which the structure will be cooled (default is 300.0 K). |
300.0
|
timestep
|
float
|
Time step for integration in femtoseconds (default is 1.0 fs). |
1.0
|
heating_rate
|
float
|
The rate at which the temperature is increased during the heating phase, in K/s (default is 1e12 K/s). |
1000000000000.0
|
cooling_rate
|
float
|
The rate at which the temperature is decreased during the cooling phase, in K/s (default is 1e12 K/s). |
1000000000000.0
|
n_print
|
int
|
The frequency of output during the simulation (default is 1000). |
1000
|
equilibration_steps
|
int | None
|
Override for all fixed equilibration stages inside the protocol. If None, each protocol uses its own hardcoded defaults. |
None
|
server_kwargs
|
dict | None
|
Additional keyword arguments for the server. |
None
|
langevin
|
bool
|
Whether to use Langevin dynamics. |
False
|
seed
|
int
|
Random seed for velocity initialization (default is 12345). Ignored if |
12345
|
tmp_working_directory
|
str | Path | None
|
Specifies the location of the temporary directory to run the simulations. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
A dictionary containing the simulation steps and temperature data. |
Example
result = melt_quench_simulation( ... structure=my_atoms, ... potential=my_potential, ... temperature_high=5000.0, ... temperature_low=300.0, ... cooling_rate=1e12 ... )
Source code in amorphouspy/src/amorphouspy/workflows/meltquench.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
parse_formula(formula: str) -> dict[str, int]
¶
Parse a chemical formula and returns a dictionary of element counts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
A string representing the chemical formula (e.g., "Al2O3"). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict[str, int]
|
A dictionary mapping element symbols to their counts. |
|
Example |
dict[str, int]
|
{"Al": 2, "O": 3} for "Al2O3". |
Source code in amorphouspy/src/amorphouspy/structure/composition.py
plan_system(composition: dict[str, float], target: int, mode: str = 'molar', target_type: str = 'atoms') -> dict
¶
Generate a comprehensive plan for the system composition and size.
This unified planner handles both 'atoms' and 'molecules' target types, converting them into a concrete allocation of formula units and atoms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
composition
|
dict[str, float]
|
A dictionary mapping oxide formulas to their fractions. |
required |
target
|
int
|
The target count (atoms or molecules depending on |
required |
mode
|
str
|
The composition mode ('molar' or 'weight'). Defaults to "molar". |
'molar'
|
target_type
|
str
|
The type of target ('atoms' or 'molecules'). Defaults to "atoms". |
'atoms'
|
Returns:
| Type | Description |
|---|---|
dict
|
A dictionary containing: - "mol_fraction": Dictionary of molar fractions. - "formula_units": Dictionary of allocated integer formula units. - "total_atoms": The actual total number of atoms. - "element_counts": Dictionary of total counts per element. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in amorphouspy/src/amorphouspy/structure/planner.py
plot_analysis_results_plotly(structure_data: StructureData) -> go.Figure
¶
Generate interactive Plotly plots for structural analysis results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure_data
|
StructureData
|
Structural analysis results. |
required |
Returns:
| Type | Description |
|---|---|
Figure
|
Interactive Plotly figure object. |
Example
fig = plot_analysis_results_plotly(structure_data) fig.show()
Source code in amorphouspy/src/amorphouspy/workflows/structural_analysis.py
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 | |
running_mean(data: list | np.ndarray, n: int) -> np.ndarray
¶
Calculate running mean of an array-like dataset.
The initial and final values of the returned array are NaN, as the running mean is not defined for those points.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
list | ndarray
|
Input data for which the running mean should be calculated. |
required |
n
|
int
|
Width of the averaging window. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of same size as input data containing the running mean values. |
Source code in amorphouspy/src/amorphouspy/shared.py
structure_from_parsed_output(initial_structure: Atoms, parsed_output: dict, *, wrap: bool = False) -> Atoms
¶
Construct an Atoms object from parsed output data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_structure
|
Atoms
|
The initial atomic structure to use as a template. |
required |
parsed_output
|
dict
|
Parsed output containing atomic positions, cell, and indices. |
required |
wrap
|
bool
|
Whether to wrap the atomic positions to the simulation cell (default is False). Keeping the unwrapped positions is more beneficial if structures are passed between different LAMMPS simulations in one workflow to ensure continuity. |
False
|
Returns:
| Type | Description |
|---|---|
Atoms
|
An |
Example
new_atoms = structure_from_parsed_output(atoms, lammps_output)
Source code in amorphouspy/src/amorphouspy/io_utils.py
temperature_scan_simulation(structure: Atoms, potential: str, temperature: list[int | float] | None = None, pressure: float = 0.0001, timestep: float = 1.0, equilibration_steps: int = 100000, production_steps: int = 200000, n_dump: int = 100000, n_log: int = 10, server_kwargs: dict[str, Any] | None = None, *, aniso: bool = False, seed: int | None = 12345, tmp_working_directory: str | Path | None = None) -> dict[Any, Any]
¶
Perform a temperature scan and collect structural data.
This workflow performs a temperature scan at the given list of temperatures. For each temperature, it equilibrates the structure at the target temperature and pressure and then performs a production MD run to collect the average volume and box lengths, needed to compute the CTE via V-T data. There differen CTEs often discussed, for example: - CTE20-300: Computed between solely as the slope from the two datapoints at 20°C and 300°C. - CTE20-600: Computed between solely as the slope from the two datapoints at 20°C and 600°C. - CTE over other arbitrary temperature range - CTE at a specific temperature based on the slope of the V-T curve at this temperature. This is often doen by fitting a linear model or higher polynomials fit to the V-T data and then taking the derivative at the temperature of interest. Because of the various options and methods to compute the CTE from V-T data, and because the actual CTE calculation is rather straightforward once the data is collected, we do not compute it directly in this workflow, but rather return the collected data for each temperature and leave the actual CTE calculation to the user.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
Input structure (assumed pre-equilibrated). |
required |
potential
|
str
|
LAMMPS potential file. |
required |
temperature
|
list[int | float] | None
|
Simulation temperature in Kelvin (default 300 K). |
None
|
pressure
|
float
|
Target pressure in GPa for NPT simulations. (default 10-4 GPa = 10^5 Pa = 1 bar). |
0.0001
|
timestep
|
float
|
MD integration timestep in femtoseconds (default 1.0 fs). |
1.0
|
equilibration_steps
|
int
|
Number of MD steps for the equilibration runs (default 100,000). |
100000
|
production_steps
|
int
|
Number of MD steps for the production runs (default 200,000). |
200000
|
n_dump
|
int
|
Dump output frequency of the production runs (default 100,000). |
100000
|
n_log
|
int
|
Log output frequency (default 10). |
10
|
server_kwargs
|
dict[str, Any] | None
|
Additional server configuration arguments. |
None
|
aniso
|
bool
|
If false, an isotropic NPT calculation is performed and the simulation box is scaled uniformly. If True, anisotropic NPT calculation is performed and the simulation box can change shape and size independently along each axis (default False). |
False
|
seed
|
int | None
|
Random seed for velocity initialization (default 12345). |
12345
|
tmp_working_directory
|
str | Path | None
|
Temporary directory for job execution. |
None
|
Returns:
| Type | Description |
|---|---|
dict[Any, Any]
|
Nested dictionary containing collected output of the simulations. The main keys are the |
dict[Any, Any]
|
temperature steps in the format "01_300K", "02_400K", etc. Under each temperature key, |
dict[Any, Any]
|
the dictionary contains another dictionary with keys "run01", "run02", ... for each |
dict[Any, Any]
|
production run. Under each run key, the dictionary contains the parsed output from the |
dict[Any, Any]
|
production run as well as computed CTE values and other thermodynamic averages. |
dict[Any, Any]
|
Structure is: |
dict[Any, Any]
|
{ "01_300K" : { "run01" : { "CTE_V" : ..., "CTE_x" : ..., "CTE_y" : ..., "CTE_z" : ..., etc}, "run02" : { "CTE_V" : ..., "CTE_x" : ..., "CTE_y" : ..., "CTE_z" : ..., etc}, ... }, "02_400K" : { "run01" : { "CTE_V" : ..., "CTE_x" : ..., "CTE_y" : ..., "CTE_z" : ..., etc}, "run02" : { "CTE_V" : ..., "CTE_x" : ..., "CTE_y" : ..., "CTE_z" : ..., etc}, ... }, ... |
dict[Any, Any]
|
} |
dict[Any, Any]
|
On the lowest level, the structure is the same as returned by |
dict[Any, Any]
|
Additionally, on the run key level, the following entries are added for bookkeeping: |
dict[Any, Any]
|
"is_converged" : bool # Whether convergence was reached within the max_production_runs |
dict[Any, Any]
|
"convergence_criterion" : float # The convergence criterion |
dict[Any, Any]
|
"structure_final" : Atoms # Final structure at this temperature |
Notes
- For every temperature, the structure is first pre-equilibrated with short (10 ps) NVT.
- Simulation settings for the NVT equilibration run are hard-coded
- CTEs are calculated sequentially if a list of temperatures is provided. Alternatively, multiple jobs with independent temperatures can be submitted to achieve parallelization.
Example
result = cte_simulation( ... structure=my_atoms, ... potential=my_potential, ... temperature=[300, 400, 500], ... production_steps=500000 ... )
Source code in amorphouspy/src/amorphouspy/workflows/cte.py
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 | |
type_to_dict(types: np.ndarray) -> dict[int, str]
¶
Generate a dictionary mapping atomic numbers (types) to element symbols from an ASE Atoms structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
types
|
ndarray
|
Array containing atom types in the simulation. |
required |
Returns:
| Type | Description |
|---|---|
dict[int, str]
|
Dictionary mapping atomic numbers to corresponding element symbols. |
Example
type_map = type_to_dict(np.array([14, 8]))
Source code in amorphouspy/src/amorphouspy/shared.py
viscosity_ensemble(structure: Atoms, potential: pd.DataFrame, n_replicas: int = 3, seeds: list[int] | None = None, base_seed: int = 12345, temperature_sim: float = 5000.0, timestep: float = 1.0, initial_production_steps: int = 10000000, n_print: int = 1, max_total_time_ns: float = 50.0, max_iterations: int = 40, eta_rel_tol: float = 0.05, eta_stable_iters: int = 3, server_kwargs: dict[str, Any] | None = None, *, langevin: bool = False, parallel: bool = False, executor: Executor | None = None, tmp_working_directory: str | Path | None = None) -> dict[str, Any]
¶
Run multiple independent viscosity simulations and return ensemble-averaged results.
Each replica starts from the same structure but uses a different random seed for velocity initialisation, producing statistically independent trajectories. The viscosity (and other properties) are averaged across replicas and the inter-replica standard deviation is reported as the uncertainty.
Execution modes (mutually exclusive; executor takes priority):
executorprovided — each replica is submitted via the executor's.submit()method. Pass any executorlib executor (SlurmJobExecutor,SlurmClusterExecutor,SingleNodeExecutor, …) to run replicas on an HPC cluster. The executor'sresource_dictis automatically populated with the MPI core count fromserver_kwargs["cores"]. The executor's lifecycle is managed by the caller.parallel=True— replicas run simultaneously on the local machine usingThreadPoolExecutor. Requiresn_replicas * server_kwargs["cores"]total cores to be available.- default (sequential) — replicas run one after another on the local machine.
Seeds are written to tmp_working_directory/viscosity_ensemble_seeds.json
immediately after generation so they are preserved even if the run is interrupted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
Input structure (assumed pre-equilibrated). |
required |
potential
|
DataFrame
|
LAMMPS potential DataFrame. |
required |
n_replicas
|
int
|
Number of independent replicas to run. Default 3. |
3
|
seeds
|
list[int] | None
|
Explicit list of integer seeds, one per replica. If |
None
|
base_seed
|
int
|
Master seed used to generate per-replica seeds when |
12345
|
temperature_sim
|
float
|
Simulation temperature in Kelvin. |
5000.0
|
timestep
|
float
|
MD timestep in fs. |
1.0
|
initial_production_steps
|
int
|
Steps for the first production run per replica. |
10000000
|
n_print
|
int
|
Thermodynamic output frequency. |
1
|
max_total_time_ns
|
float
|
Maximum total production time per replica in nanoseconds. |
50.0
|
max_iterations
|
int
|
Maximum number of 100 ps extension iterations per replica. |
40
|
eta_rel_tol
|
float
|
Relative change threshold for eta-stability check. |
0.05
|
eta_stable_iters
|
int
|
Consecutive stable iterations required per replica. |
3
|
server_kwargs
|
dict[str, Any] | None
|
Additional server arguments forwarded to LAMMPS (e.g.
|
None
|
langevin
|
bool
|
Whether to use Langevin dynamics. |
False
|
parallel
|
bool
|
If |
False
|
executor
|
Executor | None
|
Optional executorlib (or compatible) executor. When provided,
each replica is submitted as |
None
|
tmp_working_directory
|
str | Path | None
|
Directory for LAMMPS runs and seed file. |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dictionary containing:
- viscosity: Mean shear viscosity across replicas (Pa·s)
- viscosity_fit_residual: Sample standard deviation across replicas (Pa·s, ddof=1)
- viscosity_sem: Standard error of the mean (Pa·s)
- shear_modulus_inf: Mean infinite-frequency shear modulus (Pa)
- bulk_viscosity: Mean bulk viscosity (Pa·s)
- maxwell_relaxation_time_ps: Mean Maxwell relaxation time (ps)
- mean_pressure_gpa: Mean pressure averaged across replicas (GPa)
- temperature: Mean temperature across all replicas (K)
- n_replicas: Number of replicas run
- seeds: List of seeds actually used
- viscosities: Per-replica shear viscosity values (Pa·s)
- converged: Per-replica convergence flags
- results: Full |
Example
out = viscosity_ensemble( ... structure=my_atoms, ... potential=my_potential_df, ... n_replicas=3, ... temperature_sim=4000.0, ... n_print=10, ... server_kwargs={"cores": 4}, ... ) print(out["viscosity"], "±", out["viscosity_sem"], "Pa·s")
Source code in amorphouspy/src/amorphouspy/workflows/viscosity.py
941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 | |
viscosity_simulation(structure: Atoms, potential: pd.DataFrame, temperature_sim: float = 5000.0, timestep: float = 1.0, initial_production_steps: int = 10000000, n_print: int = 1, max_total_time_ns: float = 50.0, max_iterations: int = 40, eta_rel_tol: float = 0.05, eta_stable_iters: int = 3, server_kwargs: dict[str, Any] | None = None, *, langevin: bool = False, seed: int = 12345, tmp_working_directory: str | Path | None = None) -> dict[str, Any]
¶
Perform an autonomous viscosity simulation using the Helfand moment method.
Runs an initial NVT production MD (default 10 ns) and iteratively extends it by 100 ps until viscosity converges or limits are reached.
Convergence requires both conditions to be true simultaneously:
- eta-stability -
|eta_new - eta_prev| / eta_prev < eta_rel_tolforeta_stable_itersconsecutive iterations. - MSD linearity - the local slope of the Helfand moment MSD is flat
in the last 30 % of the lag window (
_viscosity_plateauedcheck), confirming that the diffusive regime has been reached.
Extensions stop when either max_total_time_ns or max_iterations
is exhausted. get_viscosity is retained as a legacy cross-check in
the return dict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Atoms
|
Input structure (assumed pre-equilibrated). |
required |
potential
|
DataFrame
|
LAMMPS potential DataFrame. |
required |
temperature_sim
|
float
|
Simulation temperature in Kelvin. |
5000.0
|
timestep
|
float
|
MD timestep in fs. |
1.0
|
initial_production_steps
|
int
|
Steps for the first production run. Default 10,000,000 (= 10 ns at 1 fs timestep). |
10000000
|
n_print
|
int
|
Thermodynamic output frequency (must equal |
1
|
max_total_time_ns
|
float
|
Maximum total production time in nanoseconds. Default 50 ns. |
50.0
|
max_iterations
|
int
|
Maximum number of 100 ps extension iterations. Default 40. |
40
|
eta_rel_tol
|
float
|
Relative change threshold for eta-stability check. Default 0.05 (5 %). |
0.05
|
eta_stable_iters
|
int
|
Consecutive stable iterations required. Default 3. |
3
|
server_kwargs
|
dict[str, Any] | None
|
Additional server arguments forwarded to LAMMPS. |
None
|
langevin
|
bool
|
Whether to use Langevin dynamics. |
False
|
seed
|
int
|
Random seed for velocity initialization. |
12345
|
tmp_working_directory
|
str | Path | None
|
Temporary directory for LAMMPS runs. |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dictionary containing:
- viscosity_data: Output of |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the potential DataFrame is empty. |
Example
result = viscosity_simulation( ... structure=my_atoms, ... potential=my_potential_df, ... temperature_sim=3000.0, ... ) converged = result["converged"] print(converged)
Source code in amorphouspy/src/amorphouspy/workflows/viscosity.py
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 | |
write_angle_distribution(bin_centers: np.ndarray, angle_hist: np.ndarray, composition: float, filepath: str, *, append: bool = False) -> None
¶
Write angle distribution to a text file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bin_centers
|
ndarray
|
Angle bin centers in degrees. |
required |
angle_hist
|
ndarray
|
Normalized angle histogram. |
required |
composition
|
float
|
Composition value (e.g., % modifier). |
required |
filepath
|
str
|
Output filepath. |
required |
append
|
bool
|
Whether to append to file. |
False
|
Example
write_angle_distribution(centers, hist, 0.5, "angles.txt")
Source code in amorphouspy/src/amorphouspy/io_utils.py
write_distribution_to_file(composition: float | str, filepath: str, dist: dict[int, int], label: str, *, append: bool = False) -> None
¶
Write a coordination/Qn histogram to a text file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
composition
|
float | str
|
Composition value to label row. |
required |
filepath
|
str
|
Output filepath. |
required |
dist
|
dict[int, int]
|
Histogram data. |
required |
label
|
str
|
Prefix for headers (e.g., Si or Q). |
required |
append
|
bool
|
Append mode; writes header only if file does not exist. |
False
|
Example
write_distribution_to_file(0.5, "qn.txt", qn_dist, "Q")
Source code in amorphouspy/src/amorphouspy/io_utils.py
write_xyz(filename: str, coords: np.ndarray, types: np.ndarray, box_size: np.ndarray | None = None, type_dict: dict[int, str] | None = None) -> None
¶
Write atomic configuration to an XYZ file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
Output XYZ file name. |
required |
coords
|
ndarray
|
Atomic coordinates. |
required |
types
|
ndarray
|
Atomic types as integers. |
required |
box_size
|
ndarray | None
|
Simulation box size in x, y, z. |
None
|
type_dict
|
dict[int, str] | None
|
Dictionary mapping atomic type integers to element symbols. |
None
|
Example
write_xyz("output.xyz", coords, types, box, {14: "Si", 8: "O"})