Source code for mammos_mumag.parameters

"""Parameters class."""

import configparser
import pathlib
from typing import Any

import mammos_entity as me
import mammos_units as u
import numpy as np
from jinja2 import Environment, PackageLoader, select_autoescape
from pydantic import ConfigDict, Field, field_validator
from pydantic.dataclasses import dataclass

from mammos_mumag.tools import check_path


[docs] @dataclass( config=ConfigDict( arbitrary_types_allowed=True, extra="forbid", validate_assignment=True ) ) class Parameters: r"""Class storing simulation parameters. Args: size: Size of the mesh. This factor usually indicates the magnitude of the geometry, i.e., 1e-9 for nanometer meshes, 1e-9 for micrometer, etc. scale: Scale of the mesh. This factor can include other scaling, so that the total scale of the mesh is `size` * `scale`. state: Name of the initial magnetization state. Scripts recognize the strings `flower`, `vortex`, `twisted`, and `random`. Other strings are interpreted as the default case. The default case is a uniformly magnetized state. h_mag_on: Whether the external field is on (True) or off (False). h_start: Strength of the external field that the hysteresis loop starts from. h_final: Strength of the external field that the hysteresis loop finishes at. h_step: Difference in field strength between two hysteresis loop measurements. h_vect: External field vector :math:`\mathbf{h}` as a list of floats or a `Vector` entity. This vector is not necessarily normal. The property `h` will be the normalized field. If not defined, the external field is zero. m_step: Threshold at which the magnetization is saved. If in the hysteresis calculation the difference between two consecutive values of magnetization along the external field vector is bigger than this value, a new configuration index will appear on the output csv table. Different configurations mean that the magnetization is behaving differently, possibly changing states. m_final: Value of magnetization (along the external field direction) at which the hysteresis calculation will stop. m_vect: Magnetization field :math:`\mathbf{m}` as a list of floats or a `Vector` entity. precond_iter: Conjugate gradient iterations for inverse Hessian approximation. tol_fun: Total energy tolerance to obtain the equilibrium configuration. tol_h_mag_factor: Factor defining the tolerance for the magnetostatic scalar potential according to the formula `tol_u = tol_fun * tol_h_mag_factor`. filepath: Path of parameter file to read at initialization. In this case all other parameters will be overwritten (if specified in the parameter file). """ size: float = 1.0e-09 scale: float = 0.0 state: str = Field(default_factory=lambda: "") h_mag_on: bool = True h_start: me.Entity = Field( default_factory=lambda: me.Entity( "ExternalMagneticField", (10 * u.T).to(u.A / u.m, equivalencies=u.magnetic_flux_field()), ) ) h_final: me.Entity = Field( default_factory=lambda: me.Entity( "ExternalMagneticField", (-10 * u.T).to(u.A / u.m, equivalencies=u.magnetic_flux_field()), ) ) h_step: me.Entity = Field( default_factory=lambda: me.Entity( "ExternalMagneticField", (-1 * u.T).to(u.A / u.m, equivalencies=u.magnetic_flux_field()), ) ) h_vect: me.Entity = Field( default_factory=lambda: me.Entity( "Vector", [0, 0, 1], ) ) m_step: me.Entity = Field( default_factory=lambda: me.Entity( "Magnetization", (1 * u.T).to(u.A / u.m, equivalencies=u.magnetic_flux_field()), ) ) m_final: me.Entity = Field( default_factory=lambda: me.Entity( "Magnetization", (-2 * u.T).to(u.A / u.m, equivalencies=u.magnetic_flux_field()), ), ) m_vect: me.Entity = Field( default_factory=lambda: me.Entity( "Vector", [0, 0, 1], ) ) precond_iter: int = 10 tol_fun: float = 1e-10 tol_h_mag_factor: float = 1.0 filepath: pathlib.Path | None = Field(default=None, repr=False) @field_validator("h_start", mode="before") @classmethod def _convert_h_start(cls, h_start: Any) -> me.Entity: """Convert h_start to the rigth Entity.""" h_start = me.Entity("ExternalMagneticField", h_start, unit=u.A / u.m) return h_start @field_validator("h_final", mode="before") @classmethod def _convert_h_final(cls, h_final: Any) -> me.Entity: """Convert h_final to the right Entity.""" h_final = me.Entity("ExternalMagneticField", h_final, unit=u.A / u.m) return h_final @field_validator("h_step", mode="before") @classmethod def _convert_h_step(cls, h_step: Any) -> me.Entity: """Convert h_step to the right Entity.""" h_step = me.Entity("ExternalMagneticField", h_step, unit=u.A / u.m) return h_step @field_validator("h_vect", mode="before") @classmethod def _convert_h_vect(cls, h_vect: Any) -> me.Entity: """Convert h_vect to the right Entity.""" h_vect = me.Entity("Vector", h_vect) if h_vect.q.size != 3: raise ValueError( f"`h_vect` has the wrong size ({h_vect.q.size} instead of 3)." ) return h_vect @field_validator("m_step", mode="before") def _convert_m_step(cls, m_step: Any) -> me.Entity: """Convert m_step to the right Entity.""" m_step = me.Entity("Magnetization", m_step, unit=u.A / u.m) return m_step @field_validator("m_final", mode="before") def _convert_m_final(cls, m_final: Any) -> me.Entity: """Convert m_final to the right Entity.""" m_final = me.Entity("Magnetization", m_final, unit=u.A / u.m) return m_final @field_validator("m_vect", mode="before") @classmethod def _convert_m_vect(cls, m_vect: Any) -> me.Entity: """Convert m_vect to the right Entity.""" m_vect = me.Entity("Vector", m_vect) if m_vect.q.size != 3: raise ValueError( f"`m_vect` has the wrong size ({m_vect.q.size} instead of 3)." ) return m_vect def __post_init__(self) -> None: """Initialize parameters with a file. If the parameters is initialized with a not-`None` `filepath` attribute, the materials files will be read automatically. """ if self.filepath is not None: self.read(self.filepath) @property def m(self) -> list[float]: """Normalized magnetization.""" return _normalize(self.m_vect) @property def h(self) -> list[float]: """Direction of the external field.""" return _normalize(self.h_vect)
[docs] def read(self, fname: str | pathlib.Path) -> None: """Read parameter file. Args: fname: File path Raises: NotImplementedError: Wrong file format. """ fpath = check_path(fname) if fpath.suffix == ".yaml": self.read_yaml(fpath) elif fpath.suffix == ".p2": self.read_p2(fpath) else: raise NotImplementedError( f"{fpath.suffix} parameter file is not supported." )
[docs] def read_p2(self, fpath: str | pathlib.Path) -> None: """Read parameter file in `p2` format. The speciality of this file format is that magnetization values are stored in Tesla for readability. Hence, they need to be converted to A/m first. Furthermore, in this format some of the names have a special formatting. Args: fpath: Parameter file path. """ u.set_enabled_equivalencies(u.magnetic_flux_field()) pars = configparser.ConfigParser() pars.read(fpath) mesh = pars["mesh"] if "size" in mesh: self.size = float(mesh["size"]) if "scale" in mesh: self.scale = float(mesh["scale"]) initial_state = pars["initial state"] if "state" in initial_state: self.state = str(initial_state["state"]) self.m_vect = [ float(initial_state["mx"]), float(initial_state["my"]), float(initial_state["mz"]), ] field = pars["field"] if "hmag_on" in field: self.h_mag_on = bool(field["hmag_on"]) self.h_start = me.Entity( "ExternalMagneticField", (float(field["hstart"]) * u.T).to(u.A / u.m) ) self.h_final = me.Entity( "ExternalMagneticField", (float(field["hfinal"]) * u.T).to(u.A / u.m) ) self.h_step = me.Entity( "ExternalMagneticField", (float(field["hstep"]) * u.T).to(u.A / u.m) ) self.h_vect = [ float(field["hx"]), float(field["hy"]), float(field["hz"]), ] if "mstep" in field: self.m_step = me.Entity( "Magnetization", (float(field["mstep"]) * u.T).to(u.A / u.m) ) if "mfinal" in field: self.m_final = me.Entity( "Magnetization", (float(field["mfinal"]) * u.T).to(u.A / u.m) ) minimizer = pars["minimizer"] if "precond_iter" in minimizer: self.precond_iter = int(minimizer["precond_iter"]) if "tol_fun" in minimizer: self.tol_fun = float(minimizer["tol_fun"]) if "tol_hmag_factor" in minimizer: self.tol_h_mag_factor = float(minimizer["tol_hmag_factor"]) if "truncation" in minimizer: self.truncation = int(minimizer["truncation"])
[docs] def read_yaml(self, fpath: str | pathlib.Path) -> None: """Read parameter file in `yaml` format. We expect the parameters to be saved using the module :py:mod:`mammos_entity.io` and use the function :py:func:`mammos_entity.io.entities_from_file`. Args: fpath: Parameter file path. """ content = me.io.entities_from_file(fpath) self.size = content.mesh_size self.scale = content.mesh_scale self.state = content.initial_state self.m_vect = [content.initial_mx, content.initial_my, content.initial_mz] self.h_mag_on = content.h_mag_on self.h_start = content.h_start self.h_final = content.h_final self.h_step = content.h_step self.h_vect = [content.hx, content.hy, content.hz] self.m_step = content.m_step self.m_final = content.m_final self.tol_fun = content.minimizer_tol_fun self.tol_h_mag_factor = content.minimizer_tol_h_mag_factor self.precond_iter = content.minimizer_precond_iter
[docs] def write_p2(self, fname: str | pathlib.Path) -> None: """Write parameter `p2` file. Args: fname: File path Examples: >>> from mammos_mumag.parameters import Parameters >>> par = Parameters() >>> par.write_p2("parameters.p2") """ u.set_enabled_equivalencies(u.magnetic_flux_field()) env = Environment( loader=PackageLoader("mammos_mumag"), autoescape=select_autoescape(), ) template = env.get_template("p2.jinja") parameters_dict = { **self.__dict__, "mx": self.m[0], "my": self.m[1], "mz": self.m[2], "hx": self.h[0], "hy": self.h[1], "hz": self.h[2], "hmag_on": int(self.h_mag_on), "hstart": self.h_start.q.to(u.T).value, "hfinal": self.h_final.q.to(u.T).value, "hstep": self.h_step.q.to(u.T).value, "mstep": self.m_step.q.to(u.T).value, "mfinal": self.m_final.q.to(u.T).value, "tol_hmag_factor": self.tol_h_mag_factor, } with open(fname, "w") as file: file.write(template.render(parameters_dict))
[docs] def write_yaml(self, fname: str | pathlib.Path) -> None: """Write parameter `yaml` file. Args: fname: File path Examples: >>> from mammos_mumag.parameters import Parameters >>> par = Parameters() >>> par.write_yaml("parameters.yaml") """ me.io.entities_to_file( fname, "File containing simulation parameters.", mesh_size=self.size, mesh_scale=self.scale, initial_state=self.state, initial_mx=self.m[0], initial_my=self.m[1], initial_mz=self.m[2], h_mag_on=self.h_mag_on, h_start=self.h_start, h_final=self.h_final, h_step=self.h_step, hx=self.h[0], hy=self.h[1], hz=self.h[2], m_step=self.m_step, m_final=self.m_final, minimizer_tol_fun=self.tol_fun, minimizer_tol_h_mag_factor=self.tol_h_mag_factor, minimizer_precond_iter=self.precond_iter, )
def _normalize(vector: me.Entity) -> list[float]: """Normalize Vector Entity and transform it into a list of float. Args: vector: 3D Vector Entity to normalize. """ v = vector.value s = np.linalg.norm(v) if s <= 1.0e-13: return list(v) else: return list(v / s)