Source code for prismatique.stem.system

# -*- coding: utf-8 -*-
# Copyright 2024 Matthew Fitzpatrick.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
r"""For specifying simulation parameters related to the modelling of STEM
systems.

"""



#####################################
## Load libraries/packages/modules ##
#####################################

# For validating and converting objects.
import czekitout.check
import czekitout.convert

# For defining classes that support enforced validation, updatability,
# pre-serialization, and de-serialization.
import fancytypes



# For validating, pre-serializing, and de-pre-serializing instances of the
# classes :class:`prismatique.sample.ModelParams`,
# :class:`prismatique.sample.PotentialSliceSubsetIDs`,
# :class:`prismatique.sample.SMatrixSubsetIDs`, and
# :class:`prismatique.sample.PotentialSliceAndSMatrixSubsetIDs`.
import prismatique.sample

# For validating instances of the :class:`prismatique.scan.rectangular.Params`,
# and for generating probe positions.
import prismatique.scan



##################################
## Define classes and functions ##
##################################

# List of public objects in objects.
__all__ = ["Params"]



def _check_and_convert_sample_specification(params):
    params["accepted_types"] = (prismatique.sample.ModelParams,
                                prismatique.sample.PotentialSliceSubsetIDs,
                                prismatique.sample.SMatrixSubsetIDs)
    
    module_alias = prismatique.sample
    func_alias = module_alias._check_and_convert_sample_specification
    sample_specification = func_alias(params)

    del params["accepted_types"]

    return sample_specification



def _pre_serialize_sample_specification(sample_specification):
    obj_to_pre_serialize = sample_specification
    module_alias = prismatique.sample
    func_alias = module_alias._pre_serialize_sample_specification
    serializable_rep = func_alias(obj_to_pre_serialize)
    
    return serializable_rep



def _de_pre_serialize_sample_specification(serializable_rep):
    module_alias = prismatique.sample
    func_alias = module_alias._de_pre_serialize_sample_specification
    sample_specification = func_alias(serializable_rep)

    return sample_specification



def _check_and_convert_probe_model_params(params):
    module_alias = prismatique.sample
    func_alias = module_alias._check_and_convert_probe_model_params
    probe_model_params = func_alias(params)

    return probe_model_params



def _pre_serialize_probe_model_params(probe_model_params):
    obj_to_pre_serialize = probe_model_params
    module_alias = prismatique.sample
    func_alias = module_alias._pre_serialize_probe_model_params
    serializable_rep = func_alias(obj_to_pre_serialize)
    
    return serializable_rep



def _de_pre_serialize_probe_model_params(serializable_rep):
    module_alias = prismatique.sample
    func_alias = module_alias._de_pre_serialize_probe_model_params
    probe_model_params = func_alias(serializable_rep)

    return probe_model_params



def _check_and_convert_specimen_tilt(params):
    obj_name = "specimen_tilt"
    kwargs = {"obj": params[obj_name], "obj_name": obj_name}
    specimen_tilt = czekitout.convert.to_pair_of_floats(**kwargs)
    
    return specimen_tilt



def _pre_serialize_specimen_tilt(specimen_tilt):
    obj_to_pre_serialize = specimen_tilt
    serializable_rep = specimen_tilt

    return serializable_rep



def _de_pre_serialize_specimen_tilt(serializable_rep):
    specimen_tilt = serializable_rep

    return specimen_tilt



def _check_and_convert_scan_config(params):
    module_alias = prismatique.scan
    func_alias = module_alias._check_and_convert_scan_config
    scan_config = func_alias(params)
    
    return scan_config



def _pre_serialize_scan_config(scan_config):
    obj_to_pre_serialize = scan_config
    if isinstance(obj_to_pre_serialize, str):
        serializable_rep = obj_to_pre_serialize
    elif isinstance(obj_to_pre_serialize, prismatique.scan.rectangular.Params):
        module_alias = prismatique.scan.rectangular
        func_alias = module_alias._pre_serialize_rectangular_scan_params
        kwargs = {"rectangular_scan_params": obj_to_pre_serialize}
        serializable_rep = func_alias(**kwargs)
    else:
        serializable_rep = scan_config

    return serializable_rep



def _de_pre_serialize_scan_config(serializable_rep):
    if isinstance(serializable_rep, str):
        scan_config = serializable_rep
    elif isinstance(serializable_rep, dict):
        module_alias = prismatique.scan.rectangular
        func_alias = module_alias._de_pre_serialize_rectangular_scan_params
        scan_config = func_alias(serializable_rep)
    else:
        func_alias = czekitout.convert.to_real_two_column_numpy_matrix
        kwargs = {"obj": serializable_rep, "obj_name": "serializable_rep"}
        scan_config = func_alias(**kwargs)

    return scan_config



_module_alias_1 = \
    prismatique.sample
_module_alias_2 = \
    prismatique.scan
_default_probe_model_params = \
    _module_alias_1._default_probe_model_params
_default_specimen_tilt = \
    (0, 0)
_default_scan_config = \
    _module_alias_2._default_scan_config
_default_skip_validation_and_conversion = \
    _module_alias_2._default_skip_validation_and_conversion



[docs]class ModelParams(fancytypes.PreSerializableAndUpdatable): r"""The simulation parameters related to the modelling of STEM systems. Parameters ---------- sample_specification : :class:`prismatique.sample.ModelParams` | :class:`prismatique.sample.PotentialSliceSubsetIDs` | :class:`prismatique.sample.SMatrixSubsetIDs` The simulation parameters specifying the sample model. If ``sample_specification`` is of the type :class:`prismatique.sample.ModelParams`, then ``sample_specifications`` specifies sample model parameters that are used to construct the model from scratch, i.e. the potential slices for each frozen phonon configuration subset are calculated from said model parameters. See the documentation for the classes :class:`prismatique.discretization.Params` and :class:`prismatique.thermal.Params` for discussions on potential slices and frozen phonon configuration subsets respectively. Otherwise, if ``sample_specification`` is an instance of the class :class:`prismatique.sample.PotentialSliceSubsetIDs`, the class :class:`prismatique.sample.SMatrixSubsetIDs`, or the class :class:`prismatique.sample.PotentialSliceAndSMatrixSubsetIDs`, then ``sample_specification`` specifies a set of files, where each file stores either the pre-calculated potential slices or :math:`S`-matrices for a frozen phonon configuration subset. See the documentation for the aforementioned classes for further discussions on specifying pre-calculated objects. See the documentation for the subpackage :mod:`prismatique.stem` for a discussion on :math:`S`-matrices. Note that any :math:`S`-matrices specified in ``sample_specification`` must be pre-calculated using the same convergence semiangle and mean beam energy as that specified by the model parameters of the probe ``probe_model_params`` below. Moreover, note that ``sample_specification`` must be an instance of the class :class:`prismatique.sample.ModelParams`, or the class :class:`prismatique.sample.PotentialSliceSubsetIDs` if using the multislice algorithm for the STEM simulation, or if the potential slices to be used in the simulation are to be saved as output, per the output parameters. probe_model_params : :class:`embeam.stem.probe.ModelParams` | `None`, optional The model parameters of the probe. See the documentation for the class :class:`embeam.stem.probe.ModelParams` for a discussion on said parameters. If ``probe_model_params`` is set to `None` [i.e. the default value], then the aforementioned model parameters are set to default values. specimen_tilt : `array_like` (`float`, shape=(``2``,)), optional ``specimen_tilt[0]`` specifies the specimen tilt along the :math:`x`-axis in mrads; ``specimen_tilt[1]`` specifies the specimen tilt along the :math:`y`-axis in mrads. Note that according to Ref. [Cowley1]_, the method used to simulate specimen tilt in ``prismatic`` is only valid for small tilts up to about 1 degree. scan_config : `array_like` (`float`, shape=(``num_positions``, ``2``)) | :class:`prismatique.scan.rectangular.Params` | `str` | `None`, optional If ``scan_config`` is a real-valued two-column matrix, then it specifies a set of probe positions, where ``scan_config[i][0]`` and ``scan_config[i][1]`` specify respectively the :math:`x`- and :math:`y`-coordinates of the probe position indexed by ``i``, in units of angstroms, with ``0<=i<num_positions`` and ``num_positions`` being the number of probe positions. If ``scan_config`` is of the type :class:`prismatique.scan.rectangular.Params`, then it specifies a rectangular grid-like pattern of probe positions. See the documentation for this class for more details. If ``scan_config`` is a string, then ``scan_config`` is a path to a file that specifies a set of probe positions. The file must be encoded as ASCII text (UTF-8). The file should be formatted as follows: the first line can be whatever header the user desires, i.e. the first line is treated as a comment; each subsequent line except for the last is of the form "x y", where "x" and "y" are the :math:`x`- and :math:`y`-coordinates of a probe position, in units of angstroms; and the last line in the file should be "-1". Since periodic boundaries conditions (PBCs) are assumed in the :math:`x`- and :math:`y`-dimensions, :math:`x`- and :math:`y`-coordinates can take on any real numbers. If ``scan_config`` is set to `None`, [i.e. the default value], then the probe is to be scanned across the entire unit cell of the simulation, in steps of 0.25 angstroms in both the :math:`x`- and :math:`y`-directions. skip_validation_and_conversion : `bool`, optional Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs` and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which being `dict` objects. Let ``params_to_be_mapped_to_core_attrs`` denote the `dict` representation of the constructor parameters excluding the parameter ``skip_validation_and_conversion``, where each `dict` key ``key`` is a different constructor parameter name, excluding the name ``"skip_validation_and_conversion"``, and ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the constructor parameter with the name given by ``key``. If ``skip_validation_and_conversion`` is set to ``False``, then for each key ``key`` in ``params_to_be_mapped_to_core_attrs``, ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key] (params_to_be_mapped_to_core_attrs)``. Otherwise, if ``skip_validation_and_conversion`` is set to ``True``, then ``core_attrs`` is set to ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired primarily when the user wants to avoid potentially expensive deep copies and/or conversions of the `dict` values of ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no copies or conversions are made in this case. """ ctor_param_names = ("sample_specification", "probe_model_params", "specimen_tilt", "scan_config") kwargs = {"namespace_as_dict": globals(), "ctor_param_names": ctor_param_names} _validation_and_conversion_funcs_ = \ fancytypes.return_validation_and_conversion_funcs(**kwargs) _pre_serialization_funcs_ = \ fancytypes.return_pre_serialization_funcs(**kwargs) _de_pre_serialization_funcs_ = \ fancytypes.return_de_pre_serialization_funcs(**kwargs) del ctor_param_names, kwargs def __init__(self, sample_specification, probe_model_params=\ _default_probe_model_params, specimen_tilt=\ _default_specimen_tilt, scan_config=\ _default_scan_config, skip_validation_and_conversion=\ _default_skip_validation_and_conversion): ctor_params = {key: val for key, val in locals().items() if (key not in ("self", "__class__"))} kwargs = ctor_params kwargs["skip_cls_tests"] = True fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs) return None
[docs] @classmethod def get_validation_and_conversion_funcs(cls): validation_and_conversion_funcs = \ cls._validation_and_conversion_funcs_.copy() return validation_and_conversion_funcs
[docs] @classmethod def get_pre_serialization_funcs(cls): pre_serialization_funcs = \ cls._pre_serialization_funcs_.copy() return pre_serialization_funcs
[docs] @classmethod def get_de_pre_serialization_funcs(cls): de_pre_serialization_funcs = \ cls._de_pre_serialization_funcs_.copy() return de_pre_serialization_funcs
def _check_and_convert_stem_system_model_params(params): obj_name = "stem_system_model_params" obj = params[obj_name] accepted_types = (ModelParams,) kwargs = {"obj": obj, "obj_name": obj_name, "accepted_types": accepted_types} czekitout.check.if_instance_of_any_accepted_types(**kwargs) kwargs = obj.get_core_attrs(deep_copy=False) stem_system_model_params = accepted_types[0](**kwargs) if "worker_params" not in params: stem_system_model_params_core_attrs = \ stem_system_model_params.get_core_attrs(deep_copy=False) sample_specification = \ stem_system_model_params_core_attrs["sample_specification"] probe_model_params = \ stem_system_model_params_core_attrs["probe_model_params"] scan_config = \ stem_system_model_params_core_attrs["scan_config"] kwargs = {"params": dict()} kwargs["params"]["sample_specification"] = sample_specification kwargs["params"]["probe_model_params"] = probe_model_params _check_and_convert_probe_model_params(**kwargs) kwargs = {"sample_specification": sample_specification, "scan_config": scan_config, "filename": None} prismatique.scan._generate_probe_positions(**kwargs) return stem_system_model_params def _pre_serialize_stem_system_model_params(stem_system_model_params): obj_to_pre_serialize = stem_system_model_params serializable_rep = obj_to_pre_serialize.pre_serialize() return serializable_rep def _de_pre_serialize_stem_system_model_params(serializable_rep): stem_system_model_params = ModelParams.de_pre_serialize(serializable_rep) return stem_system_model_params ########################### ## Define error messages ## ###########################