Source code for prismatique.hrtem.output

# -*- 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 the output parameters for HRTEM simulations.

"""



#####################################
## 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 instances of the class
# :class:`prismatique.hrtem.system.ModelParams`.
import prismatique.hrtem.system

# For validating instances of the classes
# :class:`prismatique.sample.ModelParams`, and
# :class:`prismatique.sample.PotentialSliceSubsetIDs`; and for calculating
# quantities related to the modelling of the sample.
import prismatique.sample

# For validating, pre-serializing, and de-pre-serializing instances of the class
# :class:`prismatique.hrtem.image.Params`.
import prismatique.hrtem.image

# For postprocessing HRTEM intensity images.
import prismatique._signal

# For generating tilts used in HRTEM simulations.
import prismatique.tilt



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

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



def _check_and_convert_output_dirname(params):
    module_alias = prismatique.sample
    func_alias = module_alias._check_and_convert_output_dirname
    output_dirname = func_alias(params)
    
    return output_dirname



def _pre_serialize_output_dirname(output_dirname):
    obj_to_pre_serialize = output_dirname
    serializable_rep = obj_to_pre_serialize

    return serializable_rep



def _de_pre_serialize_output_dirname(serializable_rep):
    output_dirname = serializable_rep

    return output_dirname



def _check_and_convert_max_data_size(params):
    module_alias = prismatique.sample
    func_alias = module_alias._check_and_convert_max_data_size
    max_data_size = func_alias(params)
    
    return max_data_size



def _pre_serialize_max_data_size(max_data_size):
    obj_to_pre_serialize = max_data_size
    serializable_rep = obj_to_pre_serialize

    return serializable_rep



def _de_pre_serialize_max_data_size(serializable_rep):
    max_data_size = serializable_rep

    return max_data_size



def _check_and_convert_image_params(params):
    module_alias = prismatique.hrtem.image
    func_alias = module_alias._check_and_convert_image_params
    image_params = func_alias(params)

    return image_params



def _pre_serialize_image_params(image_params):
    obj_to_pre_serialize = image_params
    module_alias = prismatique.hrtem.image
    func_alias = module_alias._pre_serialize_image_params
    serializable_rep = func_alias(obj_to_pre_serialize)

    return serializable_rep



def _de_pre_serialize_image_params(serializable_rep):
    module_alias = prismatique.hrtem.image
    func_alias = module_alias._de_pre_serialize_image_params
    image_params = func_alias(serializable_rep)

    return image_params



def _check_and_convert_save_potential_slices(params):
    obj_name = "save_potential_slices"
    kwargs = {"obj": params[obj_name], "obj_name": obj_name}
    save_potential_slices = czekitout.convert.to_bool(**kwargs)
    
    return save_potential_slices



def _pre_serialize_save_potential_slices(save_potential_slices):
    obj_to_pre_serialize = save_potential_slices
    serializable_rep = obj_to_pre_serialize

    return serializable_rep



def _de_pre_serialize_save_potential_slices(serializable_rep):
    save_potential_slices = serializable_rep

    return save_potential_slices



_module_alias_1 = \
    prismatique.sample
_default_output_dirname = \
    "sim_output_files"
_default_max_data_size = \
    _module_alias_1._default_max_data_size
_default_image_params = \
    None
_default_save_potential_slices = \
    False
_default_skip_validation_and_conversion = \
    _module_alias_1._default_skip_validation_and_conversion



[docs]class Params(fancytypes.PreSerializableAndUpdatable): r"""The output parameters for HRTEM simulations. See the documentation for the class :class:`prismatique.hrtem.image.Params` for a discussion on HRTEM image wavefunctions and intensities that is relevant to the discussion on this page. Upon the completion of a HRTEM simulation, ``prismatique`` can optionally save a variety of different HRTEM data to a set of HDF5 files based on the specifications of the user. This is done by taking the original output file generated by ``prismatic``, and then restructuring it into one or more output files. If the output parameters specify that intensity data be saved, then said data is extracted from the original ``prismatic`` output files, postprocessed, and written to a new file with basename ``"hrtem_sim_intensity_output.h5"`` in a more readable layout. This new output file has the following structure: - metadata: <HDF5 group> * r_x: <HDF5 1D dataset> + dim 1: "r_x idx" + units: "Å" * r_y: <HDF5 1D dataset> + dim 1: "r_y idx" + units: "Å" - data: <HDF5 group> * intensity_image: <HDF5 2D dataset> + dim 1: "r_y idx" + dim 2: "r_x idx" + units: "dimensionless" Note that the sub-bullet points listed immediately below a given HDF5 dataset display the HDF5 attributes associated with said HDF5 dataset. Each HDF5 scalar and dataset has a ``"units"`` attribute which, as the name suggests, indicates the units in which said data [i.e. the scalar or dataset] is expressed. Each HDF5 dataset will also have a set of attributes with names of the form ``"dim {}".format(i)`` with ``i`` being an integer ranging from 1 to the rank of said HDF5 dataset. Attribute ``"dim {}".format(i)`` of a given HDF5 dataset labels the :math:`i^{\text{th}}` dimension of the underlying array of the dataset. Most of these dimension labels should be self-explanatory but for clarification: "idx" is short for "index"; "avg" is short for "average"; and "r_x" and "r_y" refer to the :math:`x`- and :math:`y`-coordinates in the discretized real-space. If the output parameters specify that complex-valued wavefunction data be saved, then said data is extracted from the original ``prismatic`` output files, and written to a new set of files: one file per frozen phonon/atomic configuration subset. Note that, unlike the intensity data, the complex-valued wavefunction data is not postprocessed. See the documentation for the class :class:`prismatique.thermal.Params` for a discussion on frozen phonon configurations and their grouping into subsets. For the ``i`` th frozen phonon configuration subset, the corresponding wavefunction data is saved to the file with basename ``"hrtem_sim_wavefunction_output_of_subset_"+str(i)+".h5"``. Each one of these new output files has the following structure: - metadata: <HDF5 group> * tilts: <HDF5 2D dataset> + dim 1: "tilt idx" + dim 2: "vector component idx [0->x, 1->y]" + units: "mrad" * defocii: <HDF5 1D dataset> + dim 1: "defocus idx" + units: "Å" * r_x: <HDF5 1D dataset> + dim 1: "r_x idx" + units: "Å" * r_y: <HDF5 1D dataset> + dim 1: "r_y idx" + units: "Å" - data: <HDF5 group> * image_wavefunctions: <HDF5 5D dataset> + dim 1: "atomic config idx" + dim 2: "defocus idx" + dim 3: "tilt idx" + dim 4: "r_y idx" + dim 5: "r_x idx" + units: "dimensionless" ``prismatique`` can also optionally save the "potential slice" [i.e. Eq. :eq:`coarse_grained_potential_1`] data for each subset of frozen phonon configurations into separate HDF5 output files by extracting said data from the original ``prismatic`` output files. See the documentation for the class :class:`prismatique.thermal.Params` for a discussion on frozen phonon configurations their grouping into subsets. For the ``i`` th subset, the corresponding potential slice data is saved to an HDF5 output file with basename ``"potential_slices_"+str(i)+".h5"``. Unlike the output data in the file ``"hrtem_simulation_output.h5"``, the layout of the potential slice data is kept the same as that found in the original file [i.e. the same HDF5 paths are used]. The same layout needs to be used in order for ``prismatic`` to be able to successfully import/load pre-calculated potential slice data for a future simulation. It is beyond the scope of the documentation to describe the structure of the potential slice output files. Users can analyze the data in these output files with the help of the tools found in the module :mod:`prismatique.load`. The last file that is always generated after running a HRTEM simulation is a JSON file that contains, in a serialized format, the simulation parameters used. This file has the basename ``"hrtem_simulation_parameters.json"``. Parameters ---------- output_dirname : `str`, optional The relative or absolute path to the directory in which all output files are to be saved. If the directory doesn't exist upon saving the output files, it will be created if possible. max_data_size : `int`, optional The data size limit, in bytes, of the HRTEM simulation output to be generated. If the output to be generated would require a data size larger than the aforementioned limit, then an exception is raised and the HRTEM simulation is not performed. Note that data size due to HDF5 file overhead and metadata are not taken into account. image_params : :class:`prismatique.hrtem.image.Params` | `None`, optional The simulation parameters related to image wavefunctions and intensities, which includes parameters specifying what kind of image output should be saved, if any at all. If ``image_params`` is set to `None` [i.e. the default value], then the aforementioned parameters are set to default values. save_potential_slices : `bool`, optional If ``save_potential_slices`` is set to ``True``, then for each frozen phonon configuration subset, the corresponding potential slice data is written to the file with the basename ``"potential_slices_of_subset_"+str(i)+".h5"``, with ``i`` being the subset index. If ``save_potential_slices`` is set to ``False``, then no potential slice data is saved. 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 = ("output_dirname", "max_data_size", "image_params", "save_potential_slices") 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, output_dirname=\ _default_output_dirname, max_data_size=\ _default_max_data_size, image_params=\ _default_image_params, save_potential_slices=\ _default_save_potential_slices, 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_output_params(params): obj_name = "output_params" obj = params[obj_name] accepted_types = (Params, type(None)) if isinstance(obj, accepted_types[-1]): output_params = accepted_types[0]() else: 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) output_params = accepted_types[0](**kwargs) return output_params def _pre_serialize_output_params(output_params): obj_to_pre_serialize = output_params serializable_rep = obj_to_pre_serialize.pre_serialize() return serializable_rep def _de_pre_serialize_output_params(serializable_rep): output_params = Params.de_pre_serialize(serializable_rep) return output_params _default_output_params = None def _check_and_convert_hrtem_system_model_params(params): module_alias = prismatique.hrtem.system func_alias = module_alias._check_and_convert_hrtem_system_model_params hrtem_system_model_params = func_alias(params) return hrtem_system_model_params def _check_and_convert_skip_validation_and_conversion(params): module_alias = prismatique.sample func_alias = module_alias._check_and_convert_skip_validation_and_conversion skip_validation_and_conversion = func_alias(params) return skip_validation_and_conversion
[docs]def data_size(hrtem_system_model_params, output_params=\ _default_output_params, skip_validation_and_conversion=\ _default_skip_validation_and_conversion): r"""Calculate the data size of the HRTEM simulation output that one could generate according to a given HRTEM system model, and output parameter set. Note that data size due to HDF5 file overhead and metadata are not taken into account. Parameters ---------- hrtem_system_model_params : :class:`prismatique.hrtem.system.ModelParams` The simulation parameters related to the modelling of the HRTEM system. See the documentation for the class :class:`prismatique.hrtem.system.ModelParams` for a discussion on said parameters. output_params : :class:`prismatique.hrtem.output.Params` | `None`, optional The output parameters for the HRTEM simulation. See the documentation for the class :class:`prismatique.hrtem.output.Params` for a discussion on said parameters. If ``output_params`` is set to `None` [i.e. the default value], then the aforementioned simulation parameters are set to default values. skip_validation_and_conversion : `bool`, optional If ``skip_validation_and_conversion`` is set to ``False``, then validations and conversions are performed on the above parameters. Otherwise, if ``skip_validation_and_conversion`` is set to ``True``, no validations and conversions are performed on the above parameters. This option is desired primarily when the user wants to avoid potentially expensive validation and/or conversion operations. Returns ------- output_data_size : `int` The data size in units of bytes. """ params = locals() func_alias = _check_and_convert_skip_validation_and_conversion skip_validation_and_conversion = func_alias(params) if (skip_validation_and_conversion == False): global_symbol_table = globals() for param_name in params: if param_name == "skip_validation_and_conversion": continue func_name = "_check_and_convert_" + param_name func_alias = global_symbol_table[func_name] params[param_name] = func_alias(params) kwargs = params del kwargs["skip_validation_and_conversion"] output_data_size = _data_size(**kwargs) return output_data_size
def _data_size(hrtem_system_model_params, output_params): output_params_core_attrs = output_params.get_core_attrs(deep_copy=False) image_params = output_params_core_attrs["image_params"] image_params_core_attrs = image_params.get_core_attrs(deep_copy=False) output_data_size = 0 kwargs = {"hrtem_system_model_params": hrtem_system_model_params, "output_params": output_params} if image_params_core_attrs["save_final_intensity"]: output_data_size += _data_size_of_intensity_output(**kwargs) if image_params_core_attrs["save_wavefunctions"]: del kwargs["output_params"] output_data_size += _data_size_of_wavefunction_output(**kwargs) hrtem_system_model_params_core_attrs = \ hrtem_system_model_params.get_core_attrs(deep_copy=False) sample_specification = \ hrtem_system_model_params_core_attrs["sample_specification"] if output_params_core_attrs["save_potential_slices"]: kwargs = \ {"sample_specification": sample_specification} output_data_size += \ prismatique.sample._potential_slice_set_data_size(**kwargs) return output_data_size def _data_size_of_intensity_output(hrtem_system_model_params, output_params): hrtem_system_model_params_core_attrs = \ hrtem_system_model_params.get_core_attrs(deep_copy=False) output_params_core_attrs = \ output_params.get_core_attrs(deep_copy=False) sample_specification = \ hrtem_system_model_params_core_attrs["sample_specification"] image_params = output_params_core_attrs["image_params"] image_params_core_attrs = image_params.get_core_attrs(deep_copy=False) postprocessing_seq = \ image_params_core_attrs["postprocessing_seq"] num_pixels_in_postprocessed_2d_signal_space = \ prismatique._signal._num_pixels_in_postprocessed_2d_signal_space kwargs = \ {"sample_specification": sample_specification, "signal_is_cbed_pattern_set": False, "postprocessing_seq": postprocessing_seq} num_pixels_in_postprocessed_image = \ num_pixels_in_postprocessed_2d_signal_space(**kwargs) size_of_single = 4 # In bytes. data_size_of_intensity_output = (size_of_single * num_pixels_in_postprocessed_image) return data_size_of_intensity_output def _data_size_of_wavefunction_output(hrtem_system_model_params): hrtem_system_model_params_core_attrs = \ hrtem_system_model_params.get_core_attrs(deep_copy=False) sample_specification = \ hrtem_system_model_params_core_attrs["sample_specification"] tilt_params = \ hrtem_system_model_params_core_attrs["tilt_params"] gun_model_params = \ hrtem_system_model_params_core_attrs["gun_model_params"] defocal_offset_supersampling = \ hrtem_system_model_params_core_attrs["defocal_offset_supersampling"] gun_model_params_core_attrs = \ gun_model_params.get_core_attrs(deep_copy=False) mean_beam_energy = \ gun_model_params_core_attrs["mean_beam_energy"] kwargs = \ {"sample_specification": sample_specification} total_num_frozen_phonon_configs = \ prismatique.sample._total_num_frozen_phonon_configs(**kwargs) tilt_series = prismatique.tilt._series(sample_specification, mean_beam_energy, tilt_params) kwargs["signal_is_cbed_pattern_set"] = \ False num_pixels_in_unprocessed_image = \ prismatique._signal._num_pixels_in_unprocessed_2d_signal_space(**kwargs) size_of_single = 4 # In bytes. size_of_complex_single = 2 * size_of_single # In bytes. data_size_of_wavefunction_output = (total_num_frozen_phonon_configs * defocal_offset_supersampling * len(tilt_series) * num_pixels_in_unprocessed_image * size_of_complex_single) return data_size_of_wavefunction_output ########################### ## Define error messages ## ###########################