nmag-python-3 0.0.6__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.
Files changed (163) hide show
  1. {nmag_python_3-0.0.6/src/nmag_python_3.egg-info → nmag_python_3-0.0.7}/PKG-INFO +3 -3
  2. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/pyproject.toml +3 -3
  3. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/mag_material/mag_material.py +80 -10
  4. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/demag/linear.py +264 -17
  5. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/__init__.py +52 -15
  6. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/anisotropy/fields.py +8 -2
  7. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/anisotropy/materials.py +5 -3
  8. nmag_python_3-0.0.7/src/nmag/simulation/demag/bem/dirichlet.py +127 -0
  9. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/bem/operator.py +38 -4
  10. nmag_python_3-0.0.7/src/nmag/simulation/demag/fem/assembly.py +183 -0
  11. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fem/geometry.py +81 -12
  12. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fields/auxiliary.py +43 -15
  13. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fields/probe.py +54 -6
  14. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fields/recovery.py +81 -16
  15. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/exchange/coefficients.py +6 -4
  16. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/exchange/fields.py +61 -19
  17. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/exchange/llg_rhs.py +20 -7
  18. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/arrays.py +25 -6
  19. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/derived.py +5 -1
  20. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/implicit_dynamics.py +6 -1
  21. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/mesh/geometry.py +67 -6
  22. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/mesh/materials.py +189 -15
  23. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7/src/nmag_python_3.egg-info}/PKG-INFO +3 -3
  24. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/requires.txt +2 -2
  25. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/backend.py +1 -0
  26. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesh_generation.py +7 -7
  27. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesh_io.py +4 -1
  28. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/si/physical.py +17 -2
  29. nmag_python_3-0.0.6/src/nmag/simulation/demag/bem/dirichlet.py +0 -85
  30. nmag_python_3-0.0.6/src/nmag/simulation/demag/fem/assembly.py +0 -114
  31. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/LICENSE +0 -0
  32. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/README.md +0 -0
  33. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/setup.cfg +0 -0
  34. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/anisotropy/__init__.py +0 -0
  35. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/anisotropy/anisotropy.py +0 -0
  36. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/anisotropy/evaluation.py +0 -0
  37. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/anisotropy/model.py +0 -0
  38. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/anisotropy/predefined.py +0 -0
  39. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/anisotropy/py.typed +0 -0
  40. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/anisotropy/values.py +0 -0
  41. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/mag_material/__init__.py +0 -0
  42. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/mag_material/parameters.py +0 -0
  43. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/mag_material/py.typed +0 -0
  44. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/__init__.py +0 -0
  45. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/backends.py +0 -0
  46. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/checkpoint.py +0 -0
  47. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/cli/__init__.py +0 -0
  48. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/cli/nmagpp.py +0 -0
  49. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/config.py +0 -0
  50. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/demag/__init__.py +0 -0
  51. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/demag/bem_operator.py +0 -0
  52. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/demag/geometry.py +0 -0
  53. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/demag/lindholm.py +0 -0
  54. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/demag/lindholm_fast.py +0 -0
  55. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/dynamics/__init__.py +0 -0
  56. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/output.py +0 -0
  57. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/parallel.py +0 -0
  58. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/py.typed +0 -0
  59. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/resources.py +0 -0
  60. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/anisotropy/__init__.py +0 -0
  61. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/__init__.py +0 -0
  62. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/bem/__init__.py +0 -0
  63. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/bem/diagnostics.py +0 -0
  64. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/bem/hierarchical.py +0 -0
  65. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fem/__init__.py +0 -0
  66. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fem/charges.py +0 -0
  67. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/fields/__init__.py +0 -0
  68. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/demag/solver.py +0 -0
  69. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/dynamics/__init__.py +0 -0
  70. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/dynamics/advance.py +0 -0
  71. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/dynamics/integrator.py +0 -0
  72. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/exchange/__init__.py +0 -0
  73. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/__init__.py +0 -0
  74. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/availability.py +0 -0
  75. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/averages.py +0 -0
  76. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/maxangle.py +0 -0
  77. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/fields/probes.py +0 -0
  78. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/mesh/__init__.py +0 -0
  79. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/mesh/probe.py +0 -0
  80. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/restart.py +0 -0
  81. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/simulation/support.py +0 -0
  82. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag/vtk_export.py +0 -0
  83. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/SOURCES.txt +0 -0
  84. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/dependency_links.txt +0 -0
  85. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/entry_points.txt +0 -0
  86. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmag_python_3.egg-info/top_level.txt +0 -0
  87. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/__init__.py +0 -0
  88. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/cli/__init__.py +0 -0
  89. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/cli/nmeshimport.py +0 -0
  90. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/geometry/__init__.py +0 -0
  91. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/geometry/boolean_operations.py +0 -0
  92. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/geometry/primitives.py +0 -0
  93. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/geometry/transform.py +0 -0
  94. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/io/__init__.py +0 -0
  95. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/io/ascii.py +0 -0
  96. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/io/legacy_nmesh_hdf5.py +0 -0
  97. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/io/meshio_support.py +0 -0
  98. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/io/netgen.py +0 -0
  99. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesh_model.py +0 -0
  100. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesh_utilities.py +0 -0
  101. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/__init__.py +0 -0
  102. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/driver.py +0 -0
  103. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/meshing_defaults.py +0 -0
  104. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/meshing_parameters.py +0 -0
  105. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/parity.py +0 -0
  106. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/parity_canonical.py +0 -0
  107. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/parity_comparison.py +0 -0
  108. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/parity_metrics.py +0 -0
  109. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/periodic.py +0 -0
  110. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/__init__.py +0 -0
  111. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/_constants.py +0 -0
  112. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/_types.py +0 -0
  113. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/density.py +0 -0
  114. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/engine/__init__.py +0 -0
  115. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/engine/state.py +0 -0
  116. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/engine/steps.py +0 -0
  117. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/engine/topology.py +0 -0
  118. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/__init__.py +0 -0
  119. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/jit.py +0 -0
  120. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/neighbors.py +0 -0
  121. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/simplex.py +0 -0
  122. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/summary.py +0 -0
  123. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/forces/types.py +0 -0
  124. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/geometry/__init__.py +0 -0
  125. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/geometry/builder.py +0 -0
  126. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/geometry/model.py +0 -0
  127. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/seeding/__init__.py +0 -0
  128. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/seeding/periodic.py +0 -0
  129. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/seeding/points.py +0 -0
  130. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/seeding/sampling.py +0 -0
  131. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/topology/__init__.py +0 -0
  132. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/topology/finalize.py +0 -0
  133. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/relaxation/topology/recovery.py +0 -0
  134. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/mesher/sectioned_config.py +0 -0
  135. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/nmesh.py +0 -0
  136. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/py.typed +0 -0
  137. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/utils/__init__.py +0 -0
  138. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/utils/array_list_utils.py +0 -0
  139. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/utils/constants.py +0 -0
  140. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/utils/timing_memory_utils.py +0 -0
  141. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/nmesh/utils/types.py +0 -0
  142. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/si/constants.py +0 -0
  143. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/si/py.typed +0 -0
  144. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/__init__.py +0 -0
  145. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/clock.py +0 -0
  146. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/data_writer.py +0 -0
  147. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/data_writer_collection.py +0 -0
  148. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/hysteresis.py +0 -0
  149. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/hysteresis_runner.py +0 -0
  150. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/hysteresis_schedule.py +0 -0
  151. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/inference/__init__.py +0 -0
  152. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/inference/inference.py +0 -0
  153. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/py.typed +0 -0
  154. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/quantity.py +0 -0
  155. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/simulation/simulation_core.py +0 -0
  156. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/throttler/__init__.py +0 -0
  157. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/throttler/py.typed +0 -0
  158. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/throttler/throttler.py +0 -0
  159. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/when/__init__.py +0 -0
  160. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/when/py.typed +0 -0
  161. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/src/when/when.py +0 -0
  162. {nmag_python_3-0.0.6 → nmag_python_3-0.0.7}/tests/test_docs_examples.py +0 -0
  163. {nmag_python_3-0.0.6 → 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.6
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.6; extra == "docs"
28
- Requires-Dist: mkdocstrings-python==2.0.8; extra == "docs"
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.6"
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.6",
36
- "mkdocstrings-python==2.0.8",
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
- if self._derived_su_constants is None:
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
- thermal_factor = (2.0 * constants.boltzmann_constant * self.llg_damping) / (
143
- -constants.gamma0 * constants.mu0 * self.Ms
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 + self.su_llg_damping**2)
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 - self.su_llg_damping),
173
- "su_llg_stt_adiab": su_f * (1.0 + self.su_llg_damping * xi),
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 dense system."""
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
- return np.asarray(
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
- np.asarray(rhs, dtype=float),
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
- lu, pivots = _scipy_linalg().lu_factor(
43
- np.asarray(matrix, dtype=float),
44
- check_finite=False,
45
- )
46
- return ScipyLUFactorization(np.asarray(lu, dtype=float), np.asarray(pivots, dtype=int))
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
- constraint = np.ones(matrix.shape[0], dtype=float)
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
- [matrix, constraint[:, np.newaxis]],
273
+ [matrix_array, constraint[:, np.newaxis]],
54
274
  [constraint[np.newaxis, :], np.zeros((1, 1), dtype=float)],
55
275
  ]
56
276
  )
57
- return _factorize_scipy(augmented)
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
- constraint = np.ones(len(rhs), dtype=float)
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
- [matrix, constraint[:, np.newaxis]],
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
- result = linalg.solve(matrix, rhs, assume_a="sym", check_finite=False)
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
- constraint = np.ones(len(divergence), dtype=float)
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.last_demag_solve_diagnostics: dict[str, float | int] = {}
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, int] | None = None
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, int] | None = None
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._demag_fem_geometry_cache: tuple[str, Any, np.ndarray, np.ndarray] | None = None
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
185
+ ) = None
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
174
207
  ) = None
175
- self._demag_ms_values_cache: tuple[int, np.ndarray] | None = None
176
- self._demag_volume_charge_scales_cache: tuple[int, np.ndarray] | None = None
177
- self._nodal_ms_values_cache: tuple[int, np.ndarray] | None = None
178
- self._demag_aux_cache_token: tuple[int, int] | None = 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, int] | None = None
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: tuple[int, NodalMaterialCoefficients] | None = None
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[
@@ -16,13 +16,17 @@ class SimulationAnisotropyMixin(
16
16
  SimulationAnisotropyMaterialMixin,
17
17
  ):
18
18
  if TYPE_CHECKING:
19
- _anisotropy_fields_cache: tuple[tuple[int, int], np.ndarray, np.ndarray] | None
19
+ _anisotropy_fields_cache: tuple[tuple[int, int, int], np.ndarray, np.ndarray] | None
20
20
 
21
21
  def __getattr__(self, name: str) -> Any: ...
22
22
 
23
23
  def _get_anisotropy_fields(self) -> tuple[np.ndarray, np.ndarray]:
24
24
  m = np.asarray(self._fields["m"], dtype=np.float64)
25
- token = (id(self._require_mesh().raw_mesh), id(self._fields["m"]))
25
+ token = (
26
+ self._mesh_geometry_token(),
27
+ self._material_state_token(),
28
+ id(self._fields["m"]),
29
+ )
26
30
  cached = self._anisotropy_fields_cache
27
31
  if cached is not None and cached[0] == token:
28
32
  return cached[1], cached[2]
@@ -49,6 +53,8 @@ class SimulationAnisotropyMixin(
49
53
 
50
54
  if not np.all(np.isfinite(field)) or not np.all(np.isfinite(energy)):
51
55
  raise FloatingPointError("Anisotropy evaluation produced non-finite fields.")
56
+ field.setflags(write=False)
57
+ energy.setflags(write=False)
52
58
  self._anisotropy_fields_cache = (token, field, energy)
53
59
  return field, energy
54
60