Source code for mammos_mumag.materials

"""Materials class."""

import numbers
import pathlib
from typing import Any

import mammos_entity as me
import mammos_units as u
import yaml
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)) class MaterialDomain: """Uniform material domain. It collects material parameters, constant in a certain domain. """ theta: float = 0.0 """Angle of the magnetocrystalline anisotropy axis from the :math:`z`-direction in radians.""" phi: float = 0.0 """Angle of the magnetocrystalline anisotropy axis from the :math:`x`-direction in radians.""" K1: me.Entity = Field(default_factory=me.Ku()) r"""First magnetocrystalline anisotropy constant in :math:`\mathrm{J}/\mathrm{m}^3`.""" K2: me.Entity = Field(default_factory=me.Ku()) r"""Second magnetocrystalline anisotropy constant in :math:`\mathrm{J}/\mathrm{m}^3`.""" Ms: me.Entity = Field(default_factory=me.Ms()) r"""Spontaneous magnetisation in :math:`\mathrm{A}/\mathrm{m}`.""" A: me.Entity = Field(default_factory=me.A()) r"""Exchange stiffness constant in :math:`\mathrm{J}/\mathrm{m}`.""" @field_validator("K1", mode="before") @classmethod def _convert_K1(cls, K1: Any) -> Any: """Convert number or Quantity to Entity.""" if isinstance(K1, numbers.Real | u.Quantity): K1 = me.Ku(K1, unit=u.J / u.m**3) return K1 @field_validator("K2", mode="before") @classmethod def _convert_K2(cls, K2: Any) -> Any: """Convert number or Quantity to Entity.""" if isinstance(K2, float | int | u.Quantity): K2 = me.Ku(K2, unit=u.J / u.m**3) return K2 @field_validator("A", mode="before") @classmethod def _convert_A(cls, A: Any) -> Any: """Convert number or Quantity to Entity.""" if isinstance(A, float | int | u.Quantity): A = me.A(A, unit=u.J / u.m) return A @field_validator("Ms", mode="before") @classmethod def _convert_Ms(cls, Ms: Any) -> Any: """Convert number or Quantity to Entity.""" if isinstance(Ms, float | int | u.Quantity): Ms = me.Ms(Ms, unit=u.A / u.m) return Ms
[docs] @dataclass class Materials: """This class stores, reads, and writes material parameters.""" domains: list[MaterialDomain] = Field(default_factory=list) """Each domain is a MaterialDomain class of material parameters, constant in each region.""" filepath: pathlib.Path | None = Field(default=None, repr=False) """Material file path.""" def __post_init__(self) -> None: """Initialize materials with a file. If the materials is initialized with an empty `domains` attribute and with a not-`None` `filepath` attribute, the materials files will be read automatically. """ if (len(self.domains) == 0) and (self.filepath is not None): self.read(self.filepath)
[docs] def add_domain( self, A: float, Ms: float, K1: float, K2: float, phi: float, theta: float ) -> None: r"""Append domain with specified parameters. Args: A: Exchange stiffness constant in :math:`\mathrm{J}/\mathrm{m}`. Ms: Spontaneous magnetisation in :math:`\mathrm{T}`. K1: First magnetocrystalline anisotropy constant in :math:`\mathrm{J}/\mathrm{m}^3`. K2: Second magnetocrystalline anisotropy constant in :math:`\mathrm{J}/\mathrm{m}^3`. phi: Angle of the magnetocrystalline anisotropy axis from the :math:`x`-direction in radians. theta: Angle of the magnetocrystalline anisotropy axis from the :math:`z`-direction in radians. Examples: >>> from mammos_mumag.materials import Materials >>> mat = Materials() >>> mat.add_domain(A=1, Ms=2, K1=3, K2=0, phi=0, theta=0) >>> mat Materials(domains=[MaterialDomain(theta=..., phi=..., K1=..., K2=..., Ms=..., A=...)]) """ # noqa: E501 dom = MaterialDomain( theta=theta, phi=phi, K1=K1, K2=K2, Ms=Ms, A=A, ) self.domains.append(dom)
[docs] def read(self, fname: str | pathlib.Path) -> None: """Read materials file. This function overwrites the current :py:attr:`~mammos_mumag.materials.Materials.domains` attribute. Currently accepted formats: ``krn`` and ``yaml``. Args: fname: File name. Raises: NotImplementedError: Wrong file format. """ fpath = check_path(fname) if fpath.suffix == ".yaml": self.domains = read_yaml(fpath) elif fpath.suffix == ".krn": self.domains = read_krn(fpath) else: raise NotImplementedError( f"{fpath.suffix} materials file is not supported." )
[docs] def write_krn(self, fname: str | pathlib.Path) -> None: """Write material `krn` file. Each domain in :py:attr:`~domains` is written on a single line with spaces as separators. Args: fname: File path """ env = Environment( loader=PackageLoader("mammos_mumag"), autoescape=select_autoescape(), ) template = env.get_template("krn.jinja") with open(fname, "w") as file: file.write( template.render( { "domains": self.domains, "u": u, "eq": u.magnetic_flux_field(), } ) )
[docs] def write_yaml(self, fname: str | pathlib.Path) -> None: """Write material `yaml` file. Args: fname: File path """ domains = [ { "theta": dom.theta, "phi": dom.phi, "K1": dom.K1.value.tolist(), "K2": dom.K2.value.tolist(), "Ms": dom.Ms.q.to( u.T, equivalencies=u.magnetic_flux_field() ).value.tolist(), "A": dom.A.value.tolist(), } for dom in self.domains ] with open(fname, "w") as file: yaml.dump(domains, file)
[docs] def read_krn(fname: str | pathlib.Path) -> list[MaterialDomain]: """Read material `krn` file and return as list of dictionaries. Args: fname: File path Returns: Domains as list of dictionaries, with each dictionary defining the material constant in a specific region. """ with open(fname) as file: lines = file.readlines() lines = [line.split() for line in lines] return [ MaterialDomain( theta=float(line[0]), phi=float(line[1]), K1=me.Ku(float(line[2]), unit="J/m3"), K2=me.Ku(float(line[3]), unit="J/m3"), Ms=me.Ms( (float(line[4]) * u.T).to( u.A / u.m, equivalencies=u.magnetic_flux_field() ), unit="A/m", ), A=me.A(float(line[5]), unit="J/m"), ) for line in lines ]
[docs] def read_yaml(fname: str | pathlib.Path) -> list[MaterialDomain]: """Read material `yaml` file. Args: fname: File path Returns: Domains as list of dictionaries, with each dictionary defining the material constant in a specific region. """ with open(fname) as file: domains = yaml.safe_load(file) return [ MaterialDomain( theta=float(dom["theta"]), phi=float(dom["phi"]), K1=me.Ku(float(dom["K1"]), unit=u.J / u.m**3), K2=me.Ku(float(dom["K2"]), unit=u.J / u.m**3), Ms=me.Ms( (float(dom["Ms"]) * u.T).to( u.A / u.m, equivalencies=u.magnetic_flux_field() ) ), A=me.A(float(dom["A"]), unit=u.J / u.m), ) for dom in domains ]