nmag-python-3 0.0.5__tar.gz → 0.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nmag_python_3-0.0.5/src/nmag_python_3.egg-info → nmag_python_3-0.0.7}/PKG-INFO +3 -3
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/pyproject.toml +3 -3
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/mag_material/mag_material.py +80 -10
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/demag/linear.py +264 -17
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/__init__.py +68 -27
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/anisotropy/fields.py +8 -2
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/anisotropy/materials.py +5 -3
- nmag_python_3-0.0.7/src/nmag/simulation/demag/bem/dirichlet.py +127 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/bem/operator.py +38 -4
- nmag_python_3-0.0.7/src/nmag/simulation/demag/fem/assembly.py +183 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fem/geometry.py +81 -12
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fields/auxiliary.py +43 -15
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fields/probe.py +54 -6
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fields/recovery.py +81 -16
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/exchange/coefficients.py +6 -4
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/exchange/fields.py +61 -19
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/exchange/llg_rhs.py +20 -7
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/arrays.py +25 -6
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/derived.py +5 -1
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/implicit_dynamics.py +6 -1
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/mesh/geometry.py +67 -6
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/mesh/materials.py +189 -15
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7/src/nmag_python_3.egg-info}/PKG-INFO +3 -3
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/requires.txt +2 -2
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/backend.py +1 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesh_generation.py +7 -7
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesh_io.py +13 -1
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/si/physical.py +17 -2
- nmag_python_3-0.0.5/src/nmag/simulation/demag/bem/dirichlet.py +0 -85
- nmag_python_3-0.0.5/src/nmag/simulation/demag/fem/assembly.py +0 -114
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/LICENSE +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/README.md +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/setup.cfg +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/anisotropy/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/anisotropy/anisotropy.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/anisotropy/evaluation.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/anisotropy/model.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/anisotropy/predefined.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/anisotropy/py.typed +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/anisotropy/values.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/mag_material/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/mag_material/parameters.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/mag_material/py.typed +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/backends.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/checkpoint.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/cli/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/cli/nmagpp.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/config.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/demag/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/demag/bem_operator.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/demag/geometry.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/demag/lindholm.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/demag/lindholm_fast.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/dynamics/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/output.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/parallel.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/py.typed +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/resources.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/anisotropy/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/bem/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/bem/diagnostics.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/bem/hierarchical.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fem/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fem/charges.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fields/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/solver.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/dynamics/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/dynamics/advance.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/dynamics/integrator.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/exchange/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/availability.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/averages.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/maxangle.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/probes.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/mesh/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/mesh/probe.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/restart.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/simulation/support.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag/vtk_export.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/SOURCES.txt +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/dependency_links.txt +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/entry_points.txt +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/top_level.txt +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/cli/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/cli/nmeshimport.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/geometry/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/geometry/boolean_operations.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/geometry/primitives.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/geometry/transform.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/io/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/io/ascii.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/io/legacy_nmesh_hdf5.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/io/meshio_support.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/io/netgen.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesh_model.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesh_utilities.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/driver.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/meshing_defaults.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/meshing_parameters.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/parity.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/parity_canonical.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/parity_comparison.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/parity_metrics.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/periodic.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/_constants.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/_types.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/density.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/engine/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/engine/state.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/engine/steps.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/engine/topology.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/jit.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/neighbors.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/simplex.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/summary.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/types.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/geometry/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/geometry/builder.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/geometry/model.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/seeding/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/seeding/periodic.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/seeding/points.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/seeding/sampling.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/topology/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/topology/finalize.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/topology/recovery.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/mesher/sectioned_config.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/nmesh.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/py.typed +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/utils/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/utils/array_list_utils.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/utils/constants.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/utils/timing_memory_utils.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/nmesh/utils/types.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/si/constants.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/si/py.typed +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/clock.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/data_writer.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/data_writer_collection.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/hysteresis.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/hysteresis_runner.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/hysteresis_schedule.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/inference/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/inference/inference.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/py.typed +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/quantity.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/simulation/simulation_core.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/throttler/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/throttler/py.typed +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/throttler/throttler.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/when/__init__.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/when/py.typed +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/src/when/when.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/tests/test_docs_examples.py +0 -0
- {nmag_python_3-0.0.5 → nmag_python_3-0.0.7}/tests/test_packaging_hygiene.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nmag-python-3
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: Standalone Python 3 implementation of the Nmag micromagnetic simulation interface
|
|
5
5
|
Author-email: TriMagnetix <info@trimagnetix.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -24,8 +24,8 @@ Requires-Dist: maturin<2,>=1.14; extra == "rust"
|
|
|
24
24
|
Provides-Extra: docs
|
|
25
25
|
Requires-Dist: mkdocs==1.6.1; extra == "docs"
|
|
26
26
|
Requires-Dist: mkdocs-material==9.7.7; extra == "docs"
|
|
27
|
-
Requires-Dist: mkdocstrings==1.0.
|
|
28
|
-
Requires-Dist: mkdocstrings-python==2.0.
|
|
27
|
+
Requires-Dist: mkdocstrings==1.0.8; extra == "docs"
|
|
28
|
+
Requires-Dist: mkdocstrings-python==2.0.10; extra == "docs"
|
|
29
29
|
Provides-Extra: dev
|
|
30
30
|
Requires-Dist: maturin<2,>=1.14; extra == "dev"
|
|
31
31
|
Requires-Dist: h5py-stubs<0.2,>=0.1.2; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nmag-python-3"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.7"
|
|
8
8
|
authors = [{ name = "TriMagnetix", email = "info@trimagnetix.com" }]
|
|
9
9
|
description = "Standalone Python 3 implementation of the Nmag micromagnetic simulation interface"
|
|
10
10
|
readme = "README.md"
|
|
@@ -32,8 +32,8 @@ rust = ["maturin>=1.14,<2"]
|
|
|
32
32
|
docs = [
|
|
33
33
|
"mkdocs==1.6.1",
|
|
34
34
|
"mkdocs-material==9.7.7",
|
|
35
|
-
"mkdocstrings==1.0.
|
|
36
|
-
"mkdocstrings-python==2.0.
|
|
35
|
+
"mkdocstrings==1.0.8",
|
|
36
|
+
"mkdocstrings-python==2.0.10",
|
|
37
37
|
]
|
|
38
38
|
dev = [
|
|
39
39
|
"maturin>=1.14,<2",
|
|
@@ -131,18 +131,85 @@ class MagMaterial:
|
|
|
131
131
|
|
|
132
132
|
self.su_anisotropy = self.anisotropy
|
|
133
133
|
self._derived_su_constants: dict[str, MaterialScalar] | None = None
|
|
134
|
+
self._derived_su_token: tuple[int, ...] | None = None
|
|
134
135
|
|
|
135
136
|
self.extended_print = False
|
|
137
|
+
self._initialized = True
|
|
136
138
|
if log.isEnabledFor(logging.INFO):
|
|
137
139
|
log.info("Created new Material:\n %s", self)
|
|
138
140
|
|
|
141
|
+
def _sync_llg_coeffs(self) -> None:
|
|
142
|
+
gilbert_to_ll = 1.0 / (1.0 + self.su_llg_damping**2)
|
|
143
|
+
base_coeff1 = -self.su_llg_gamma_G * gilbert_to_ll
|
|
144
|
+
object.__setattr__(self, "su_llg_coeff2", base_coeff1 * self.su_llg_damping)
|
|
145
|
+
object.__setattr__(
|
|
146
|
+
self,
|
|
147
|
+
"su_llg_coeff1",
|
|
148
|
+
0.0 if not self.do_precession else base_coeff1,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
152
|
+
object.__setattr__(self, name, value)
|
|
153
|
+
if not getattr(self, "_initialized", False):
|
|
154
|
+
return
|
|
155
|
+
if name == "Ms":
|
|
156
|
+
object.__setattr__(self, "su_Ms", value)
|
|
157
|
+
elif name == "llg_gamma_G":
|
|
158
|
+
object.__setattr__(self, "su_llg_gamma_G", value)
|
|
159
|
+
self._sync_llg_coeffs()
|
|
160
|
+
elif name == "llg_damping":
|
|
161
|
+
object.__setattr__(self, "su_llg_damping", value)
|
|
162
|
+
self._sync_llg_coeffs()
|
|
163
|
+
elif name in {"su_llg_gamma_G", "su_llg_damping", "do_precession"}:
|
|
164
|
+
self._sync_llg_coeffs()
|
|
165
|
+
elif name == "llg_normalisationfactor":
|
|
166
|
+
object.__setattr__(self, "su_llg_normalisationfactor", value)
|
|
167
|
+
elif name == "exchange_coupling":
|
|
168
|
+
object.__setattr__(self, "su_exchange_coupling", value)
|
|
169
|
+
elif name == "anisotropy":
|
|
170
|
+
object.__setattr__(self, "su_anisotropy", value)
|
|
171
|
+
if not name.startswith("_"):
|
|
172
|
+
object.__setattr__(self, "_derived_su_constants", None)
|
|
173
|
+
object.__setattr__(self, "_derived_su_token", None)
|
|
174
|
+
|
|
139
175
|
def _derived_su_values(self) -> dict[str, MaterialScalar]:
|
|
140
|
-
|
|
176
|
+
token = (
|
|
177
|
+
id(self.Ms),
|
|
178
|
+
id(self.llg_damping),
|
|
179
|
+
id(self.su_llg_damping),
|
|
180
|
+
id(self.llg_gamma_G),
|
|
181
|
+
id(self.su_llg_gamma_G),
|
|
182
|
+
id(self.llg_normalisationfactor),
|
|
183
|
+
id(self.su_llg_normalisationfactor),
|
|
184
|
+
id(self.llg_polarisation),
|
|
185
|
+
id(self.llg_xi),
|
|
186
|
+
id(self.exchange_coupling),
|
|
187
|
+
id(self.su_exchange_coupling),
|
|
188
|
+
id(self.su_Ms),
|
|
189
|
+
bool(self.do_precession),
|
|
190
|
+
id(self.anisotropy),
|
|
191
|
+
id(self.su_anisotropy),
|
|
192
|
+
)
|
|
193
|
+
if self._derived_su_constants is None or self._derived_su_token != token:
|
|
141
194
|
constants = _si_constants()
|
|
142
|
-
|
|
143
|
-
|
|
195
|
+
ms = self.Ms.in_units_of(SI("A/m"))
|
|
196
|
+
su_ms = self.su_Ms.in_units_of(SI("A/m"))
|
|
197
|
+
thermal_factor = (
|
|
198
|
+
(
|
|
199
|
+
(2.0 * constants.boltzmann_constant * self.llg_damping)
|
|
200
|
+
/ (-constants.gamma0 * constants.mu0 * SI(1.0, "A/m"))
|
|
201
|
+
)
|
|
202
|
+
* 0.0
|
|
203
|
+
if ms == 0.0
|
|
204
|
+
else (2.0 * constants.boltzmann_constant * self.llg_damping)
|
|
205
|
+
/ (-constants.gamma0 * constants.mu0 * self.Ms)
|
|
206
|
+
)
|
|
207
|
+
damping = (
|
|
208
|
+
self.su_llg_damping.in_units_of(SI(1))
|
|
209
|
+
if isinstance(self.su_llg_damping, SI)
|
|
210
|
+
else float(self.su_llg_damping)
|
|
144
211
|
)
|
|
145
|
-
gilbert_to_ll = 1.0 / (1.0 +
|
|
212
|
+
gilbert_to_ll = 1.0 / (1.0 + damping * damping)
|
|
146
213
|
polarisation = (
|
|
147
214
|
self.llg_polarisation.in_units_of(SI(1))
|
|
148
215
|
if isinstance(self.llg_polarisation, SI)
|
|
@@ -153,7 +220,6 @@ class MagMaterial:
|
|
|
153
220
|
cast(Any, 1.0 * constants.bohr_magneton).to("J/T").magnitude
|
|
154
221
|
)
|
|
155
222
|
charge = float(cast(Any, 1.0 * constants.positron_charge).to("C").magnitude)
|
|
156
|
-
ms = self.Ms.in_units_of(SI("A/m"))
|
|
157
223
|
su_f = (
|
|
158
224
|
SI(0.0, "m^3/A/s")
|
|
159
225
|
if ms == 0.0
|
|
@@ -165,16 +231,20 @@ class MagMaterial:
|
|
|
165
231
|
"m^3/A/s",
|
|
166
232
|
)
|
|
167
233
|
)
|
|
234
|
+
su_exch_prefactor = (
|
|
235
|
+
(2.0 * self.su_exchange_coupling / (constants.mu0 * SI(1.0, "A/m"))) * 0.0
|
|
236
|
+
if su_ms == 0.0
|
|
237
|
+
else 2.0 * self.su_exchange_coupling / (constants.mu0 * self.su_Ms)
|
|
238
|
+
)
|
|
168
239
|
self._derived_su_constants = {
|
|
169
240
|
"thermal_factor": thermal_factor,
|
|
170
241
|
"su_thermal_factor": thermal_factor,
|
|
171
242
|
"su_llg_stt_prefactor": 0.0 if su_f == 0.0 else 1.0,
|
|
172
|
-
"su_llg_stt_nadiab": su_f * (xi -
|
|
173
|
-
"su_llg_stt_adiab": su_f * (1.0 +
|
|
174
|
-
"su_exch_prefactor":
|
|
175
|
-
2.0 * self.su_exchange_coupling / (constants.mu0 * self.su_Ms)
|
|
176
|
-
),
|
|
243
|
+
"su_llg_stt_nadiab": su_f * (xi - damping),
|
|
244
|
+
"su_llg_stt_adiab": su_f * (1.0 + damping * xi),
|
|
245
|
+
"su_exch_prefactor": su_exch_prefactor,
|
|
177
246
|
}
|
|
247
|
+
self._derived_su_token = token
|
|
178
248
|
return self._derived_su_constants
|
|
179
249
|
|
|
180
250
|
@property
|
|
@@ -11,6 +11,7 @@ import numpy as np
|
|
|
11
11
|
|
|
12
12
|
from ..backends import (
|
|
13
13
|
DEMAG_DENSE_MAX_POINTS_ENV,
|
|
14
|
+
DEMAG_FEM_MATRIX_BACKEND_ENV,
|
|
14
15
|
LEAST_SQUARES_RELATIVE_RESIDUAL_TOLERANCE,
|
|
15
16
|
_demag_solve_condition_diagnostics_enabled,
|
|
16
17
|
_selected_demag_linear_solver_backend,
|
|
@@ -20,41 +21,267 @@ LOGGER = logging.getLogger(__name__)
|
|
|
20
21
|
_scipy_linalg_cache: Any | None = None
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
_SPARSE_FACTORIZATION_MIN_POINTS = 64
|
|
25
|
+
_SPARSE_FACTORIZATION_MAX_DENSITY = 0.25
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _allow_sparse_factorization() -> bool:
|
|
29
|
+
return os.environ.get(DEMAG_FEM_MATRIX_BACKEND_ENV, "auto").strip().lower() != "dense"
|
|
30
|
+
|
|
31
|
+
|
|
23
32
|
@dataclass(frozen=True, slots=True)
|
|
24
33
|
class ScipyLUFactorization:
|
|
25
|
-
"""Reusable LU factorization for a geometry-dependent
|
|
34
|
+
"""Reusable LU factorization for a geometry-dependent linear system."""
|
|
26
35
|
|
|
27
36
|
lu: np.ndarray
|
|
28
37
|
pivots: np.ndarray
|
|
38
|
+
splu: Any | None = None
|
|
39
|
+
gauge_fixed: bool = False
|
|
40
|
+
matrix: np.ndarray | None = None
|
|
41
|
+
constraint_scale: float = 1.0
|
|
29
42
|
|
|
30
43
|
def solve(self, rhs: np.ndarray) -> np.ndarray:
|
|
31
|
-
|
|
44
|
+
rhs_array = np.asarray(rhs, dtype=float)
|
|
45
|
+
if not np.all(np.isfinite(rhs_array)):
|
|
46
|
+
raise np.linalg.LinAlgError("Cannot solve linear system with non-finite RHS.")
|
|
47
|
+
if self.splu is not None:
|
|
48
|
+
if self.gauge_fixed:
|
|
49
|
+
n = rhs_array.shape[0] - 1
|
|
50
|
+
if n < 0:
|
|
51
|
+
raise ValueError(f"Expected rhs of length >= 1, got {rhs_array.shape}.")
|
|
52
|
+
rhs_body = rhs_array[:n]
|
|
53
|
+
rhs_gauge = rhs_array[n]
|
|
54
|
+
if n == 0 and np.any(rhs_gauge != 0.0):
|
|
55
|
+
raise np.linalg.LinAlgError(
|
|
56
|
+
"Singular gauge-fixed system with non-zero constraint RHS."
|
|
57
|
+
)
|
|
58
|
+
lam = np.mean(rhs_body, axis=0) if n > 0 else np.zeros_like(rhs_gauge)
|
|
59
|
+
rhs_centered = rhs_body - lam
|
|
60
|
+
out = np.zeros_like(rhs_array, dtype=float)
|
|
61
|
+
if n > 1:
|
|
62
|
+
out[1:n] = self.splu.solve(rhs_centered[1:])
|
|
63
|
+
if n > 0:
|
|
64
|
+
out[:n] += rhs_gauge / n - np.mean(out[:n], axis=0)
|
|
65
|
+
out[n] = lam
|
|
66
|
+
if not np.all(np.isfinite(out)):
|
|
67
|
+
raise np.linalg.LinAlgError("Sparse gauge-fixed LU solve produced non-finite values.")
|
|
68
|
+
return out
|
|
69
|
+
out = np.asarray(self.splu.solve(rhs_array), dtype=float)
|
|
70
|
+
if not np.all(np.isfinite(out)):
|
|
71
|
+
raise np.linalg.LinAlgError("Sparse LU solve produced non-finite values.")
|
|
72
|
+
return out
|
|
73
|
+
solve_rhs = rhs_array
|
|
74
|
+
if self.gauge_fixed and self.constraint_scale != 1.0 and rhs_array.shape[0] > 0:
|
|
75
|
+
solve_rhs = rhs_array.copy()
|
|
76
|
+
solve_rhs[-1] *= self.constraint_scale
|
|
77
|
+
if self.matrix is not None:
|
|
78
|
+
linalg = _scipy_linalg()
|
|
79
|
+
out = _validated_least_squares_solution(
|
|
80
|
+
self.matrix,
|
|
81
|
+
solve_rhs,
|
|
82
|
+
np.asarray(linalg.lstsq(self.matrix, solve_rhs, check_finite=False)[0], dtype=float),
|
|
83
|
+
)
|
|
84
|
+
if self.gauge_fixed and self.constraint_scale != 1.0 and out.shape[0] > 0:
|
|
85
|
+
out = out.copy()
|
|
86
|
+
out[-1] *= self.constraint_scale
|
|
87
|
+
return out
|
|
88
|
+
out = np.asarray(
|
|
32
89
|
_scipy_linalg().lu_solve(
|
|
33
90
|
(self.lu, self.pivots),
|
|
34
|
-
|
|
91
|
+
solve_rhs,
|
|
35
92
|
check_finite=False,
|
|
36
93
|
),
|
|
37
94
|
dtype=float,
|
|
38
95
|
)
|
|
96
|
+
if not np.all(np.isfinite(out)):
|
|
97
|
+
raise np.linalg.LinAlgError("Dense LU solve produced non-finite values.")
|
|
98
|
+
if self.gauge_fixed and self.constraint_scale != 1.0 and out.shape[0] > 0:
|
|
99
|
+
out = out.copy()
|
|
100
|
+
out[-1] *= self.constraint_scale
|
|
101
|
+
return out
|
|
102
|
+
|
|
103
|
+
def solve_gauge(self, divergence: np.ndarray) -> np.ndarray:
|
|
104
|
+
div_array = np.asarray(divergence, dtype=float)
|
|
105
|
+
if not np.all(np.isfinite(div_array)):
|
|
106
|
+
raise np.linalg.LinAlgError("Cannot solve gauge-fixed system with non-finite RHS.")
|
|
107
|
+
if self.splu is not None and self.gauge_fixed:
|
|
108
|
+
n = div_array.shape[0]
|
|
109
|
+
if n <= 1:
|
|
110
|
+
return np.zeros_like(div_array, dtype=float)
|
|
111
|
+
rhs_centered = div_array - np.mean(div_array, axis=0)
|
|
112
|
+
phi = np.zeros_like(div_array, dtype=float)
|
|
113
|
+
phi[1:] = self.splu.solve(rhs_centered[1:])
|
|
114
|
+
phi -= np.mean(phi, axis=0)
|
|
115
|
+
if not np.all(np.isfinite(phi)):
|
|
116
|
+
raise np.linalg.LinAlgError("Sparse gauge-fixed LU solve produced non-finite values.")
|
|
117
|
+
return phi
|
|
118
|
+
augmented_shape = (div_array.shape[0] + 1, *div_array.shape[1:])
|
|
119
|
+
augmented_rhs = np.zeros(augmented_shape, dtype=float)
|
|
120
|
+
augmented_rhs[:-1] = div_array
|
|
121
|
+
return self.solve(augmented_rhs)[:-1]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _is_well_conditioned_splu(splu: Any, matrix_scale: float) -> bool:
|
|
125
|
+
if matrix_scale <= np.finfo(float).tiny or not np.isfinite(matrix_scale):
|
|
126
|
+
return False
|
|
127
|
+
u_diag = np.asarray(splu.U.diagonal(), dtype=float)
|
|
128
|
+
if u_diag.size == 0 or not np.all(np.isfinite(u_diag)):
|
|
129
|
+
return False
|
|
130
|
+
if not np.all(np.isfinite(splu.U.data)) or not np.all(np.isfinite(splu.L.data)):
|
|
131
|
+
return False
|
|
132
|
+
max_u = max(float(np.max(np.abs(splu.U.data))) if splu.U.nnz else 0.0, matrix_scale)
|
|
133
|
+
pivot_tol = 100.0 * np.finfo(float).eps * max_u
|
|
134
|
+
return bool(np.all(np.abs(u_diag) > pivot_tol))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _factorize_dense_scipy(matrix_array: np.ndarray) -> ScipyLUFactorization:
|
|
138
|
+
import warnings
|
|
139
|
+
|
|
140
|
+
if not np.all(np.isfinite(matrix_array)):
|
|
141
|
+
raise np.linalg.LinAlgError("Cannot factorize a matrix with non-finite entries.")
|
|
142
|
+
|
|
143
|
+
linalg = _scipy_linalg()
|
|
144
|
+
with warnings.catch_warnings():
|
|
145
|
+
warnings.simplefilter("ignore", getattr(linalg, "LinAlgWarning", Warning))
|
|
146
|
+
lu, pivots = linalg.lu_factor(
|
|
147
|
+
matrix_array,
|
|
148
|
+
check_finite=False,
|
|
149
|
+
)
|
|
150
|
+
lu_array = np.asarray(lu, dtype=float)
|
|
151
|
+
pivots_array = np.asarray(pivots, dtype=int)
|
|
152
|
+
finite_lu = bool(np.all(np.isfinite(lu_array)))
|
|
153
|
+
max_entry = float(np.max(np.abs(lu_array))) if (lu_array.size and finite_lu) else 0.0
|
|
154
|
+
retained_matrix = (
|
|
155
|
+
matrix_array
|
|
156
|
+
if (
|
|
157
|
+
lu_array.size == 0
|
|
158
|
+
or not finite_lu
|
|
159
|
+
or max_entry <= np.finfo(float).tiny
|
|
160
|
+
or np.any(np.abs(np.diag(lu_array)) <= 100.0 * np.finfo(float).eps * max_entry)
|
|
161
|
+
)
|
|
162
|
+
else None
|
|
163
|
+
)
|
|
164
|
+
return ScipyLUFactorization(
|
|
165
|
+
lu_array,
|
|
166
|
+
pivots_array,
|
|
167
|
+
matrix=retained_matrix,
|
|
168
|
+
)
|
|
39
169
|
|
|
40
170
|
|
|
41
171
|
def _factorize_scipy(matrix: np.ndarray) -> ScipyLUFactorization:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
172
|
+
matrix_array = np.asarray(matrix, dtype=float)
|
|
173
|
+
if (
|
|
174
|
+
_allow_sparse_factorization()
|
|
175
|
+
and matrix_array.ndim == 2
|
|
176
|
+
and matrix_array.shape[0] == matrix_array.shape[1]
|
|
177
|
+
and matrix_array.shape[0] >= _SPARSE_FACTORIZATION_MIN_POINTS
|
|
178
|
+
and np.all(np.isfinite(matrix_array))
|
|
179
|
+
):
|
|
180
|
+
sparse, sparse_linalg = _scipy_sparse_modules()
|
|
181
|
+
csc = sparse.csc_matrix(matrix_array)
|
|
182
|
+
max_abs = float(np.max(np.abs(csc.data))) if csc.nnz else 0.0
|
|
183
|
+
if (
|
|
184
|
+
max_abs > np.finfo(float).tiny
|
|
185
|
+
and csc.nnz <= _SPARSE_FACTORIZATION_MAX_DENSITY * matrix_array.size
|
|
186
|
+
):
|
|
187
|
+
try:
|
|
188
|
+
splu = sparse_linalg.splu(csc)
|
|
189
|
+
if _is_well_conditioned_splu(splu, max_abs):
|
|
190
|
+
return ScipyLUFactorization(
|
|
191
|
+
np.empty((0, 0), dtype=float),
|
|
192
|
+
np.empty(0, dtype=int),
|
|
193
|
+
splu=splu,
|
|
194
|
+
gauge_fixed=False,
|
|
195
|
+
)
|
|
196
|
+
except (RuntimeError, np.linalg.LinAlgError):
|
|
197
|
+
LOGGER.debug(
|
|
198
|
+
"Sparse LU factorization failed; falling back to dense LU.",
|
|
199
|
+
exc_info=True,
|
|
200
|
+
)
|
|
201
|
+
return _factorize_dense_scipy(matrix_array)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _gauge_constraint_scale(matrix: np.ndarray) -> float:
|
|
205
|
+
if matrix.size == 0 or not np.all(np.isfinite(matrix)):
|
|
206
|
+
return 1.0
|
|
207
|
+
if matrix.ndim == 2 and matrix.shape[0] == matrix.shape[1]:
|
|
208
|
+
diag_max = float(np.max(np.abs(np.diag(matrix))))
|
|
209
|
+
if diag_max > np.finfo(float).tiny:
|
|
210
|
+
return diag_max
|
|
211
|
+
entry_max = float(np.max(np.abs(matrix)))
|
|
212
|
+
if entry_max > np.finfo(float).tiny:
|
|
213
|
+
return entry_max
|
|
214
|
+
return 1.0
|
|
47
215
|
|
|
48
216
|
|
|
49
217
|
def _factorize_gauge_fixed(matrix: np.ndarray) -> ScipyLUFactorization:
|
|
50
|
-
|
|
218
|
+
matrix_array = np.asarray(matrix, dtype=float)
|
|
219
|
+
n = matrix_array.shape[0]
|
|
220
|
+
if (
|
|
221
|
+
_allow_sparse_factorization()
|
|
222
|
+
and matrix_array.ndim == 2
|
|
223
|
+
and matrix_array.shape[1] == n
|
|
224
|
+
and n >= _SPARSE_FACTORIZATION_MIN_POINTS
|
|
225
|
+
and np.all(np.isfinite(matrix_array))
|
|
226
|
+
):
|
|
227
|
+
sparse, sparse_linalg = _scipy_sparse_modules()
|
|
228
|
+
csc = sparse.csc_matrix(matrix_array)
|
|
229
|
+
max_abs = float(np.max(np.abs(csc.data))) if csc.nnz else 0.0
|
|
230
|
+
if (
|
|
231
|
+
max_abs > np.finfo(float).tiny
|
|
232
|
+
and csc.nnz <= _SPARSE_FACTORIZATION_MAX_DENSITY * matrix_array.size
|
|
233
|
+
):
|
|
234
|
+
n_components = sparse.csgraph.connected_components(
|
|
235
|
+
csc,
|
|
236
|
+
directed=False,
|
|
237
|
+
return_labels=False,
|
|
238
|
+
)
|
|
239
|
+
row_col_tol = 100.0 * np.finfo(float).eps * max_abs
|
|
240
|
+
if (
|
|
241
|
+
n_components == 1
|
|
242
|
+
and np.allclose(
|
|
243
|
+
np.asarray(csc.sum(axis=1)).ravel(),
|
|
244
|
+
0.0,
|
|
245
|
+
atol=row_col_tol,
|
|
246
|
+
rtol=0.0,
|
|
247
|
+
)
|
|
248
|
+
and np.allclose(
|
|
249
|
+
np.asarray(csc.sum(axis=0)).ravel(),
|
|
250
|
+
0.0,
|
|
251
|
+
atol=row_col_tol,
|
|
252
|
+
rtol=0.0,
|
|
253
|
+
)
|
|
254
|
+
):
|
|
255
|
+
try:
|
|
256
|
+
splu = sparse_linalg.splu(csc[1:, 1:])
|
|
257
|
+
if _is_well_conditioned_splu(splu, max_abs):
|
|
258
|
+
return ScipyLUFactorization(
|
|
259
|
+
np.empty((0, 0), dtype=float),
|
|
260
|
+
np.empty(0, dtype=int),
|
|
261
|
+
splu=splu,
|
|
262
|
+
gauge_fixed=True,
|
|
263
|
+
)
|
|
264
|
+
except (RuntimeError, np.linalg.LinAlgError):
|
|
265
|
+
LOGGER.debug(
|
|
266
|
+
"Sparse gauge-fixed LU factorization failed; falling back to dense LU.",
|
|
267
|
+
exc_info=True,
|
|
268
|
+
)
|
|
269
|
+
constraint_scale = _gauge_constraint_scale(matrix_array)
|
|
270
|
+
constraint = np.full(n, constraint_scale, dtype=float)
|
|
51
271
|
augmented = np.block(
|
|
52
272
|
[
|
|
53
|
-
[
|
|
273
|
+
[matrix_array, constraint[:, np.newaxis]],
|
|
54
274
|
[constraint[np.newaxis, :], np.zeros((1, 1), dtype=float)],
|
|
55
275
|
]
|
|
56
276
|
)
|
|
57
|
-
|
|
277
|
+
dense = _factorize_dense_scipy(augmented)
|
|
278
|
+
return ScipyLUFactorization(
|
|
279
|
+
dense.lu,
|
|
280
|
+
dense.pivots,
|
|
281
|
+
matrix=dense.matrix,
|
|
282
|
+
gauge_fixed=True,
|
|
283
|
+
constraint_scale=constraint_scale,
|
|
284
|
+
)
|
|
58
285
|
|
|
59
286
|
|
|
60
287
|
def __getattr__(name: str) -> Any:
|
|
@@ -304,10 +531,12 @@ def _gauge_augmented_system(
|
|
|
304
531
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
305
532
|
started = time.perf_counter()
|
|
306
533
|
try:
|
|
307
|
-
|
|
534
|
+
matrix_array = np.asarray(matrix, dtype=float)
|
|
535
|
+
constraint_scale = _gauge_constraint_scale(matrix_array)
|
|
536
|
+
constraint = np.full(len(rhs), constraint_scale, dtype=float)
|
|
308
537
|
augmented = np.block(
|
|
309
538
|
[
|
|
310
|
-
[
|
|
539
|
+
[matrix_array, constraint[:, np.newaxis]],
|
|
311
540
|
[constraint[np.newaxis, :], np.zeros((1, 1), dtype=float)],
|
|
312
541
|
]
|
|
313
542
|
)
|
|
@@ -337,17 +566,29 @@ def _solve_dense_scipy(
|
|
|
337
566
|
record_timing: Callable[[str, float], None] | None,
|
|
338
567
|
timing_prefix: str,
|
|
339
568
|
) -> np.ndarray:
|
|
569
|
+
import warnings
|
|
570
|
+
|
|
340
571
|
started = time.perf_counter()
|
|
341
572
|
linalg = _scipy_linalg()
|
|
342
573
|
if record_timing is not None:
|
|
343
574
|
record_timing(f"{timing_prefix}:scipy_import", time.perf_counter() - started)
|
|
344
575
|
started = time.perf_counter()
|
|
345
576
|
try:
|
|
346
|
-
|
|
577
|
+
with warnings.catch_warnings():
|
|
578
|
+
warnings.simplefilter("error", getattr(linalg, "LinAlgWarning", Warning))
|
|
579
|
+
result = np.asarray(
|
|
580
|
+
linalg.solve(matrix, rhs, assume_a="sym", check_finite=False),
|
|
581
|
+
dtype=float,
|
|
582
|
+
)
|
|
583
|
+
if not np.all(np.isfinite(result)) or (
|
|
584
|
+
_safe_relative_residual(matrix @ result - rhs, rhs)
|
|
585
|
+
> LEAST_SQUARES_RELATIVE_RESIDUAL_TOLERANCE
|
|
586
|
+
):
|
|
587
|
+
raise np.linalg.LinAlgError("Direct SciPy solve failed residual validation.")
|
|
347
588
|
if record_timing is not None:
|
|
348
589
|
record_timing(f"{timing_prefix}:scipy_solve", time.perf_counter() - started)
|
|
349
590
|
return result
|
|
350
|
-
except np.linalg.LinAlgError:
|
|
591
|
+
except (np.linalg.LinAlgError, Warning):
|
|
351
592
|
if record_timing is not None:
|
|
352
593
|
record_timing(
|
|
353
594
|
f"{timing_prefix}:scipy_solve_failed",
|
|
@@ -387,7 +628,12 @@ def _solve_dense_numpy(
|
|
|
387
628
|
) -> np.ndarray:
|
|
388
629
|
started = time.perf_counter()
|
|
389
630
|
try:
|
|
390
|
-
result = np.linalg.solve(matrix, rhs)
|
|
631
|
+
result = np.asarray(np.linalg.solve(matrix, rhs), dtype=float)
|
|
632
|
+
if not np.all(np.isfinite(result)) or (
|
|
633
|
+
_safe_relative_residual(matrix @ result - rhs, rhs)
|
|
634
|
+
> LEAST_SQUARES_RELATIVE_RESIDUAL_TOLERANCE
|
|
635
|
+
):
|
|
636
|
+
raise np.linalg.LinAlgError("Direct NumPy solve failed residual validation.")
|
|
391
637
|
if record_timing is not None:
|
|
392
638
|
record_timing(f"{timing_prefix}:numpy_solve", time.perf_counter() - started)
|
|
393
639
|
return result
|
|
@@ -470,7 +716,8 @@ def _demag_solve_diagnostics(
|
|
|
470
716
|
if _is_sparse_matrix(stiffness):
|
|
471
717
|
diagnostics["condition_numbers_skipped_for_sparse_storage"] = 1
|
|
472
718
|
else:
|
|
473
|
-
|
|
719
|
+
constraint_scale = _gauge_constraint_scale(np.asarray(stiffness, dtype=float))
|
|
720
|
+
constraint = np.full(len(divergence), constraint_scale, dtype=float)
|
|
474
721
|
augmented_stiffness = np.block(
|
|
475
722
|
[
|
|
476
723
|
[stiffness, constraint[:, np.newaxis]],
|
|
@@ -145,7 +145,11 @@ class Simulation(
|
|
|
145
145
|
self.last_probe_timings_seconds: dict[str, float] = {}
|
|
146
146
|
self.last_subfield_average_timings_seconds: dict[str, float] = {}
|
|
147
147
|
self.last_maxangle_timings_seconds: dict[str, float] = {}
|
|
148
|
-
self.
|
|
148
|
+
self._last_demag_solve_diagnostics: dict[str, float | int] | None = {}
|
|
149
|
+
self._last_demag_solve_diagnostics_inputs: (
|
|
150
|
+
tuple[Any, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]
|
|
151
|
+
| None
|
|
152
|
+
) = None
|
|
149
153
|
self._last_bem_operator_stats: BemOperatorStats | None = None
|
|
150
154
|
self.last_demag_internal_vectors: dict[str, np.ndarray] = {}
|
|
151
155
|
self._active_subfield_array_timings: dict[str, float] | None = None
|
|
@@ -159,45 +163,78 @@ class Simulation(
|
|
|
159
163
|
self.mat_of_mat_name: dict[str, Any] = {}
|
|
160
164
|
self.materials: list[Any] = []
|
|
161
165
|
self._fields: dict[str, Any] = {}
|
|
162
|
-
self._demag_cache_token: tuple[int,
|
|
166
|
+
self._demag_cache_token: tuple[int, ...] | None = None
|
|
163
167
|
self._demag_dipoles: tuple[np.ndarray, np.ndarray, float] | None = None
|
|
164
168
|
self._demag_nodal_cache: np.ndarray | None = None
|
|
165
|
-
self._demag_cell_cache_token: tuple[int,
|
|
169
|
+
self._demag_cell_cache_token: tuple[int, ...] | None = None
|
|
166
170
|
self._demag_cell_field_cache: tuple[np.ndarray, np.ndarray] | None = None
|
|
167
171
|
self._demag_geometry_cache_token: int | None = None
|
|
168
172
|
self._demag_boundary_faces_cache: list[tuple[int, tuple[int, int, int]]] | None = None
|
|
169
173
|
self._demag_bem_cache: tuple[str, np.ndarray, Any, BemOperatorStats] | None = None
|
|
170
|
-
self.
|
|
174
|
+
self._demag_bem_env_cache: tuple[Any, ...] | None = None
|
|
175
|
+
self._demag_fem_geometry_cache: (
|
|
176
|
+
tuple[str, np.ndarray, np.ndarray, Any, np.ndarray, np.ndarray] | None
|
|
177
|
+
) = None
|
|
178
|
+
self._demag_fem_env_cache: tuple[Any, ...] | None = None
|
|
171
179
|
self._demag_gauge_factorization_cache: ScipyLUFactorization | None = None
|
|
172
180
|
self._demag_dirichlet_factorization_cache: (
|
|
173
|
-
tuple[np.ndarray, np.ndarray, ScipyLUFactorization] | None
|
|
181
|
+
tuple[np.ndarray, np.ndarray, Any, ScipyLUFactorization] | None
|
|
182
|
+
) = None
|
|
183
|
+
self._demag_nodal_recovery_operator_cache: (
|
|
184
|
+
tuple[np.ndarray, np.ndarray, np.ndarray, int, Any] | None
|
|
174
185
|
) = None
|
|
175
|
-
self.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
self.
|
|
186
|
+
self._demag_fem_divergence_operator_cache: (
|
|
187
|
+
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, int, Any] | None
|
|
188
|
+
) = None
|
|
189
|
+
self._demag_cell_gradient_inputs_cache: (
|
|
190
|
+
tuple[
|
|
191
|
+
np.ndarray,
|
|
192
|
+
np.ndarray,
|
|
193
|
+
np.ndarray,
|
|
194
|
+
np.ndarray,
|
|
195
|
+
np.ndarray,
|
|
196
|
+
np.ndarray,
|
|
197
|
+
np.ndarray,
|
|
198
|
+
np.ndarray,
|
|
199
|
+
np.ndarray,
|
|
200
|
+
np.ndarray,
|
|
201
|
+
]
|
|
202
|
+
| None
|
|
203
|
+
) = None
|
|
204
|
+
self._demag_ms_values_cache: tuple[int, int, Any, np.ndarray, np.ndarray] | None = None
|
|
205
|
+
self._demag_volume_charge_scales_cache: (
|
|
206
|
+
tuple[int, int, Any, np.ndarray, np.ndarray] | None
|
|
207
|
+
) = None
|
|
208
|
+
self._nodal_ms_values_cache: tuple[tuple[int, int], np.ndarray] | None = None
|
|
209
|
+
self._demag_aux_cache_token: tuple[int, ...] | None = None
|
|
179
210
|
self._demag_phi_cache: np.ndarray | None = None
|
|
180
211
|
self._demag_rho_cache: np.ndarray | None = None
|
|
181
212
|
self._demag_volumes_cache: np.ndarray | None = None
|
|
182
|
-
self._mesh_points_cache: tuple[int, np.ndarray] | None = None
|
|
213
|
+
self._mesh_points_cache: tuple[int, Any, int, np.ndarray] | None = None
|
|
214
|
+
self._mesh_simplices_cache: tuple[int, Any, int, np.ndarray] | None = None
|
|
183
215
|
self._mesh_bounds_cache: tuple[int, np.ndarray, np.ndarray, np.ndarray] | None = None
|
|
184
216
|
self._mesh_edge_cache: tuple[int, np.ndarray] | None = None
|
|
185
217
|
self._simplex_volume_cache: tuple[int, np.ndarray, np.ndarray] | None = None
|
|
186
218
|
self._volume_average_node_weights_cache: tuple[int, int, np.ndarray] | None = None
|
|
187
219
|
self._incident_cell_volume_sums_cache: tuple[int, int, int, np.ndarray] | None = None
|
|
188
|
-
self._exchange_cache_token: tuple[int,
|
|
220
|
+
self._exchange_cache_token: tuple[int, ...] | None = None
|
|
189
221
|
self._exchange_nodal_cache: np.ndarray | None = None
|
|
222
|
+
self._exchange_operator_cache: (
|
|
223
|
+
tuple[int, Any, np.ndarray, Any, Any, np.ndarray, bool, np.ndarray] | None
|
|
224
|
+
) = None
|
|
190
225
|
self._exchange_spectral_bound_cache: tuple[int, float] | None = None
|
|
191
|
-
self._nodal_material_coefficients_cache:
|
|
226
|
+
self._nodal_material_coefficients_cache: (
|
|
227
|
+
tuple[tuple[int, int], NodalMaterialCoefficients] | None
|
|
228
|
+
) = None
|
|
192
229
|
self._nodal_anisotropy_groups_cache: (
|
|
193
|
-
tuple[int, tuple[NodalAnisotropyGroup, ...]] | None
|
|
230
|
+
tuple[tuple[int, int], tuple[NodalAnisotropyGroup, ...]] | None
|
|
194
231
|
) = None
|
|
195
232
|
self._anisotropy_fields_cache: tuple[
|
|
196
|
-
tuple[int, int],
|
|
233
|
+
tuple[int, int, int],
|
|
197
234
|
np.ndarray,
|
|
198
235
|
np.ndarray,
|
|
199
236
|
] | None = None
|
|
200
|
-
self._llg_affine_operator_cache: tuple[int, np.ndarray] | None = None
|
|
237
|
+
self._llg_affine_operator_cache: tuple[tuple[int, int, bool], np.ndarray] | None = None
|
|
201
238
|
self._probe_geometry_cache_token: int | None = None
|
|
202
239
|
self._probe_tetrahedral_cache: (
|
|
203
240
|
tuple[
|
|
@@ -264,7 +301,8 @@ class Simulation(
|
|
|
264
301
|
Args:
|
|
265
302
|
filename: Legacy Nmesh or Meshio-supported mesh file.
|
|
266
303
|
region_names_and_mag_mats: ``(region_name, material)`` pairs in
|
|
267
|
-
ascending mesh-region order
|
|
304
|
+
ascending mesh-region order. Mesh region IDs do not need to be
|
|
305
|
+
contiguous or start at region 1.
|
|
268
306
|
unit_length: Physical length represented by one mesh coordinate
|
|
269
307
|
unit.
|
|
270
308
|
do_reorder: Request legacy node reordering. Reordering is currently
|
|
@@ -298,21 +336,24 @@ class Simulation(
|
|
|
298
336
|
if manual_distribution is not None:
|
|
299
337
|
self.mesh.set_vertex_distribution(manual_distribution)
|
|
300
338
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
self.region_id_of_name = {name: index for index, name in self.region_name_of_id.items()}
|
|
306
|
-
|
|
307
|
-
mesh_region_ids = {int(region_id) for region_id in self.mesh.regions}
|
|
308
|
-
configured_region_ids = set(self.region_name_of_id)
|
|
309
|
-
if mesh_region_ids != configured_region_ids:
|
|
339
|
+
mesh_region_ids = sorted({int(region_id) for region_id in self.mesh.regions})
|
|
340
|
+
if len(mesh_region_ids) != len(region_names_and_mag_mats):
|
|
341
|
+
configured_names = [name for name, _ in region_names_and_mag_mats]
|
|
310
342
|
raise ValueError(
|
|
311
343
|
"Mesh material regions do not match the configured region list: "
|
|
312
|
-
f"mesh={
|
|
313
|
-
"
|
|
344
|
+
f"mesh={mesh_region_ids}, configured={configured_names}. "
|
|
345
|
+
"Supply one material per mesh region, in ascending mesh-region order."
|
|
314
346
|
)
|
|
315
347
|
|
|
348
|
+
self.region_name_list = [name for name, _ in region_names_and_mag_mats]
|
|
349
|
+
self.region_name_of_id = {
|
|
350
|
+
region_id: name
|
|
351
|
+
for region_id, name in zip(mesh_region_ids, self.region_name_list, strict=True)
|
|
352
|
+
}
|
|
353
|
+
self.region_id_of_name = {
|
|
354
|
+
name: region_id for region_id, name in self.region_name_of_id.items()
|
|
355
|
+
}
|
|
356
|
+
|
|
316
357
|
self.mats_of_region_name = {}
|
|
317
358
|
self.mat_of_mat_name = {}
|
|
318
359
|
for name, materials in region_names_and_mag_mats:
|