-
Notifications
You must be signed in to change notification settings - Fork 4
Feature/SOF-7782 Maxwell displacement #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
632e564
update: add maxwell disorder (cursor)
VsevolodX f841bfe
update: add maxwell disorder test
VsevolodX f11da25
chore: move
VsevolodX 962ff0f
chore: update test
VsevolodX 227ae3f
update: use OOP
VsevolodX 8f763dc
Merge branch 'main' into feature/SOF-7782
VsevolodX ccc8247
update: remove temperatuirew
VsevolodX 6a3ecb6
update: generalize and reuse
VsevolodX baa5121
update: cleanup test
VsevolodX 1b326ab
update: calibrate disorder param
VsevolodX 079b4fb
update: add calibration param
VsevolodX b7d9d8a
update: use periodic table
VsevolodX fd396f1
update: remove index
VsevolodX a25acdb
update: get mass from material
VsevolodX 238ca41
chore: lint fix
VsevolodX 6b2fcb6
chore: lint fix
VsevolodX 7efdef7
chore: rename
VsevolodX b8d006e
chore: rename
VsevolodX 9bfe7eb
chore: cleanup
VsevolodX b70cd23
update: disorder in eV
VsevolodX 92102c0
chore: fix test
VsevolodX 44d62a2
chore: add description
VsevolodX dadf6bd
chore: lint fix
VsevolodX File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 9 additions & 1 deletion
10
src/py/mat3ra/made/tools/build_components/operations/core/modifications/perturb/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,15 @@ | ||
| from .functions import FunctionHolder, PerturbationFunctionHolder, SineWavePerturbationFunctionHolder | ||
| from .functions import ( | ||
| AtomicMassDependentFunctionHolder, | ||
| FunctionHolder, | ||
| MaxwellBoltzmannDisplacementHolder, | ||
| PerturbationFunctionHolder, | ||
| SineWavePerturbationFunctionHolder, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| "AtomicMassDependentFunctionHolder", | ||
| "FunctionHolder", | ||
| "MaxwellBoltzmannDisplacementHolder", | ||
| "PerturbationFunctionHolder", | ||
| "SineWavePerturbationFunctionHolder", | ||
| ] |
4 changes: 4 additions & 0 deletions
4
...a/made/tools/build_components/operations/core/modifications/perturb/functions/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,13 @@ | ||
| from .atomic_mass_dependent_function_holder import AtomicMassDependentFunctionHolder | ||
| from .function_holder import FunctionHolder | ||
| from .maxwell_boltzmann import MaxwellBoltzmannDisplacementHolder | ||
| from .perturbation_function_holder import PerturbationFunctionHolder | ||
| from .sine_wave_perturbation_function_holder import SineWavePerturbationFunctionHolder | ||
|
|
||
| __all__ = [ | ||
| "AtomicMassDependentFunctionHolder", | ||
| "FunctionHolder", | ||
| "MaxwellBoltzmannDisplacementHolder", | ||
| "PerturbationFunctionHolder", | ||
| "SineWavePerturbationFunctionHolder", | ||
| ] |
32 changes: 32 additions & 0 deletions
32
.../operations/core/modifications/perturb/functions/atomic_mass_dependent_function_holder.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| from typing import Any, List, Optional, Union | ||
|
|
||
| import sympy as sp | ||
| from mat3ra.periodic_table.helpers import get_atomic_mass_from_element | ||
|
|
||
| from .function_holder import FunctionHolder | ||
|
|
||
|
|
||
| class AtomicMassDependentFunctionHolder(FunctionHolder): | ||
| variables: List[str] = ["x", "y", "z", "m"] | ||
|
|
||
| def __init__( | ||
| self, | ||
| function: Union[sp.Expr, str] = sp.Symbol("f"), | ||
| variables: Optional[List[str]] = None, | ||
| **data: Any, | ||
| ): | ||
| if variables is None: | ||
| expr = self._to_expr(function) | ||
| vs = sorted(expr.free_symbols, key=lambda s: s.name) | ||
| variables = [str(v) for v in vs] or ["x", "y", "z", "m"] | ||
|
|
||
| super().__init__(function=function, variables=variables, **data) | ||
|
|
||
| @staticmethod | ||
| def get_atomic_mass(coordinate: List[float], material) -> float: | ||
| if material is None: | ||
| raise ValueError("Material is required to extract atomic mass") | ||
|
|
||
| atom_id = material.basis.coordinates.get_element_id_by_value(coordinate) | ||
| element = material.basis.elements.get_element_value_by_index(atom_id) | ||
| return get_atomic_mass_from_element(element) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
...ols/build_components/operations/core/modifications/perturb/functions/maxwell_boltzmann.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| from typing import Any, List, Optional | ||
|
|
||
| import numpy as np | ||
| from pydantic import Field, model_validator | ||
|
|
||
| from .atomic_mass_dependent_function_holder import AtomicMassDependentFunctionHolder | ||
|
|
||
| DEFAULT_DISORDER_PARAMETER = 1.0 | ||
|
|
||
|
|
||
| class MaxwellBoltzmannDisplacementHolder(AtomicMassDependentFunctionHolder): | ||
| disorder_parameter: float = Field( | ||
| default=DEFAULT_DISORDER_PARAMETER, | ||
| exclude=True, | ||
| description="Disorder parameter. Can be viewed as effective temperature in eV.", | ||
| ) | ||
| random_seed: Optional[int] = Field(default=None, exclude=True) | ||
| random_state: Any = Field(default=None, exclude=True) | ||
| is_mass_used: bool = Field(default=True, exclude=True) | ||
|
|
||
| @model_validator(mode="after") | ||
| def setup_random_state(self): | ||
| if self.random_state is None: | ||
| self.random_state = np.random.RandomState(self.random_seed) if self.random_seed is not None else np.random | ||
| return self | ||
|
|
||
| def apply_function(self, coordinate, material=None) -> List[float]: | ||
| if material is None: | ||
| raise ValueError("MaxwellBoltzmannDisplacementHolder requires 'material' kwargs") | ||
|
|
||
| if self.is_mass_used: | ||
| mass = self.get_atomic_mass(coordinate, material) | ||
| variance = self.disorder_parameter / mass | ||
| else: | ||
| variance = self.disorder_parameter | ||
|
|
||
| std_dev = np.sqrt(variance) | ||
| displacement = self.random_state.normal(0.0, std_dev, size=3) | ||
| return displacement.tolist() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import numpy as np | ||
| import pytest | ||
| from mat3ra.made.material import Material | ||
| from mat3ra.made.tools.build_components.operations.core.modifications.perturb.functions.maxwell_boltzmann import ( | ||
| MaxwellBoltzmannDisplacementHolder, | ||
| ) | ||
| from mat3ra.made.tools.build_components.operations.core.modifications.perturb.helpers import create_maxwell_displacement | ||
| from mat3ra.made.tools.helpers import create_supercell | ||
| from mat3ra.periodic_table.helpers import get_atomic_mass_from_element | ||
|
|
||
| from .fixtures.bulk import BULK_Si_PRIMITIVE | ||
| from .fixtures.slab import SI_CONVENTIONAL_SLAB_001 | ||
|
|
||
| DISORDER_PARAMETER = 1.0 # Temperature-like | ||
| RANDOM_SEED = 42 | ||
| NUM_SAMPLES_FOR_MSD = 1000 | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("random_seed", [None, 42, 123]) | ||
| def test_maxwell_displacement_deterministic(random_seed): | ||
| material = Material.create(BULK_Si_PRIMITIVE) | ||
| displacement_func1 = MaxwellBoltzmannDisplacementHolder( | ||
| disorder_parameter=DISORDER_PARAMETER, random_seed=random_seed | ||
| ) | ||
| displacement_func2 = MaxwellBoltzmannDisplacementHolder( | ||
| disorder_parameter=DISORDER_PARAMETER, random_seed=random_seed | ||
| ) | ||
|
|
||
| coord = [0.0, 0.0, 0.0] | ||
|
|
||
| if random_seed is not None: | ||
| disp1 = displacement_func1.apply_function(coord, material=material) | ||
| disp2 = displacement_func2.apply_function(coord, material=material) | ||
| assert np.allclose(disp1, disp2) | ||
|
|
||
| # Different seed should give different results | ||
| displacement_func3 = MaxwellBoltzmannDisplacementHolder( | ||
| disorder_parameter=DISORDER_PARAMETER, random_seed=random_seed + 1 | ||
| ) | ||
| disp3 = displacement_func3.apply_function(coord, material=material) | ||
| assert not np.allclose(disp1, disp3) or np.allclose(disp1, [0, 0, 0], atol=1e-10) | ||
| else: | ||
| # No seed: different instances should give different results (non-deterministic) | ||
| disp1 = displacement_func1.apply_function(coord, material=material) | ||
| disp2 = displacement_func2.apply_function(coord, material=material) | ||
| assert not np.allclose(disp1, disp2) or np.allclose(disp1, [0, 0, 0], atol=1e-10) | ||
|
|
||
|
|
||
| def test_maxwell_displacement_perturb_integration(): | ||
| material = Material.create(BULK_Si_PRIMITIVE) | ||
| original_coords = [coord[:] for coord in material.basis.coordinates.values] | ||
|
|
||
| perturbed_material = create_maxwell_displacement( | ||
| material, disorder_parameter=DISORDER_PARAMETER, random_seed=RANDOM_SEED | ||
| ) | ||
|
|
||
| assert len(perturbed_material.basis.coordinates.values) == len(original_coords) | ||
| for i, (orig, pert) in enumerate(zip(original_coords, perturbed_material.basis.coordinates.values)): | ||
| delta = np.array(pert) - np.array(orig) | ||
| assert np.linalg.norm(delta) > 0 or np.allclose(delta, 0, atol=1e-10) | ||
|
|
||
|
|
||
| def test_maxwell_displacement_msd_expectation(): | ||
| material = Material.create(BULK_Si_PRIMITIVE) | ||
| si_mass = get_atomic_mass_from_element("Si") | ||
| disorder_parameter = DISORDER_PARAMETER | ||
| expected_variance = disorder_parameter / si_mass | ||
| expected_msd = 3 * expected_variance | ||
|
|
||
| displacements = [] | ||
| coord = [0.0, 0.0, 0.0] | ||
| for _ in range(NUM_SAMPLES_FOR_MSD): | ||
| displacement_func = MaxwellBoltzmannDisplacementHolder(disorder_parameter=disorder_parameter, random_seed=None) | ||
| disp = displacement_func.apply_function(coord, material=material) | ||
| displacements.append(disp) | ||
|
|
||
| displacements_array = np.array(displacements) | ||
| msd = np.mean(np.sum(displacements_array**2, axis=1)) | ||
|
|
||
| assert abs(msd - expected_msd) / expected_msd < 0.3 | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "slab_config, temperature_k, random_seed", | ||
| [ | ||
| (SI_CONVENTIONAL_SLAB_001, 1300.0, 42), | ||
| (SI_CONVENTIONAL_SLAB_001, 1300.0, 42), | ||
| ], | ||
| ) | ||
| def test_maxwell_boltzmann_on_slab(slab_config, temperature_k, random_seed): | ||
| material = Material.create(slab_config) | ||
| material = create_supercell(material, scaling_factor=[4, 4, 1]) | ||
| original_coords = [coord[:] for coord in material.basis.coordinates.values] | ||
| original_lattice = material.lattice.vector_arrays.copy() | ||
|
|
||
| perturbed_material = create_maxwell_displacement( | ||
| material, disorder_parameter=temperature_k, random_seed=random_seed | ||
| ) | ||
|
|
||
| assert len(perturbed_material.basis.coordinates.values) == len(original_coords) | ||
| assert len(perturbed_material.basis.elements.values) == len(material.basis.elements.values) | ||
|
|
||
| coordinate_changes = [] | ||
| for i, (orig, pert) in enumerate(zip(original_coords, perturbed_material.basis.coordinates.values)): | ||
| delta = np.array(pert) - np.array(orig) | ||
| displacement_magnitude = np.linalg.norm(delta) | ||
| coordinate_changes.append(displacement_magnitude) | ||
|
|
||
| max_displacement = max(coordinate_changes) | ||
| mean_displacement = np.mean(coordinate_changes) | ||
|
|
||
| assert max_displacement > 0 | ||
| assert mean_displacement > 0 | ||
|
|
||
| si_mass = get_atomic_mass_from_element("Si") | ||
| expected_std = np.sqrt(temperature_k / si_mass) | ||
|
|
||
| assert mean_displacement < 5 * expected_std | ||
|
|
||
| assert np.allclose(perturbed_material.lattice.vector_arrays, original_lattice, atol=1e-10) | ||
|
|
||
| for i, element in enumerate(material.basis.elements.values): | ||
| assert perturbed_material.basis.elements.values[i] == element |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Send the element symbol together with this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Send the whole material with it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the loop: