import json
import os
from dataclasses import dataclass
from crystals import Crystal
from uncertainties import UFloat, ufloat, ufloat_fromstr
from . import utils
from .config import ENCODING, JSON_INDENT
from .paths import MeasurementPaths
[docs]@dataclass
class RefinedPhase:
"""Class that represents a refined phase."""
cif_file: str = None
def __post_init__(self):
if self.cif_file is not None:
self._crystal = self._cryst_from_cif()
def _cryst_from_cif(self) -> Crystal:
"""Get crystal objects for the phase."""
if os.path.isfile(self.cif_file):
return Crystal.from_cif(self.cif_file)
@property
def chemical_formula(self) -> float:
return self._crystal.chemical_formula
[docs] @classmethod
def from_name(cls, name: str, paths: MeasurementPaths):
"""Alternative constructor to initialise instance by phase name."""
return cls(paths.get_cif_file_path(name))
@property
def lp(self) -> float:
return self._crystal.lattice_parameters
@property
def lp_a(self) -> float:
return self._crystal.lattice_parameters[0]
@property
def lp_b(self) -> float:
return self._crystal.lattice_parameters[1]
@property
def lp_c(self) -> float:
return self._crystal.lattice_parameters[2]
@property
def lp_alpha(self) -> float:
return self._crystal.lattice_parameters[3]
@property
def lp_beta(self) -> float:
return self._crystal.lattice_parameters[4]
@property
def lp_gamma(self) -> float:
return self._crystal.lattice_parameters[5]
[docs]@dataclass
class RefinementResult:
"""Class to represent a refinement results."""
r_wp: float = None
r_exp: float = None
gof: float = None
chi_sqrd: float = None
zero_shift: float = None
sample_discplacement: float = None
composition: dict[str, float | UFloat] = None
software: str = None
version: str = None
[docs] @classmethod
def from_json(cls, file_path: str = None, json_str: str = None):
"""
Alternative constructor to initiate a RefinementResult object from a JSON string.
:param file_path: path to the JSON file to read from
:param json_str: JSON string containing the Meta attributes
Either the `file_path` or `json_str` parameter must be provided.
If both are provided, the `json_str` parameter takes precedence.
:return: RefinementResult object with attributes initialized from the JSON string
"""
if file_path is not None:
utils.ensure_file_exists(file_path)
with open(file_path, "r", encoding=ENCODING) as fobj:
json_str = fobj.read()
kwargs = json.loads(json_str)
for k, v in kwargs["composition"].items():
kwargs["composition"][k] = ufloat_fromstr(v)
return cls(**kwargs)
[docs] def to_json(
self, file_path: str, indent: int = JSON_INDENT, encoding: str = ENCODING
) -> None:
"""Write RefinementResults as JSON string to file."""
kwargs = self.__dict__.copy()
kwargs = {k: v for k, v in kwargs.items() if v is not None}
for k, v in kwargs["composition"].items():
if isinstance(v, UFloat):
kwargs["composition"][k] = str(v)
utils.write_to_json(
file_path=file_path, data=kwargs, indent=indent, encoding=encoding
)