Source code for porereax.molecule_structure

"""
Module for sampling molecular structure statistics.

The module provides :class:`MoleculeStructureSampler` to sample bonding environments of all atomtypes and reveal the atom structures.
"""

import numpy as np
import porereax.utils as utils
import matplotlib.pyplot as plt
import os

from porereax.meta_sampler import Sampler


[docs] class MoleculeStructureSampler(Sampler): """ Sampler class for molecule structure analysis. """ def __init__(self, name_out: str, dimension: str, region, process_id: int, atom_lib: dict, masses: dict, num_frames: int, box: np.ndarray, system_properties: dict): valid_dimensions = ["MoleculeStructure"] if not isinstance(dimension, str) or dimension not in valid_dimensions: raise ValueError(f"MoleculeStructureSampler does not support dimension {dimension}") super().__init__(name_out, dimension, region, process_id, atom_lib, masses, num_frames, box, system_properties) # Setup data self._data["num_frames"] = 0 self._data["structure_counts"] = {} for atom_type in atom_lib.values(): self._data["structure_counts"][atom_type] = {}
[docs] def sample(self, frame_id: int, mol_index: dict, mol_bonds: dict, bond_mask: dict, frame: object, bond_enum: object, positions_transformed: np.ndarray): atom_types = frame.particles.particle_types.array bond_topology = frame.particles.bonds.topology.array positions = frame.particles.positions.array position_mask = self._region(positions) for atom_type in self._data["structure_counts"]: atoms = np.where(atom_types == atom_type)[0] for atom in atoms: bonds = list(bond_enum.bonds_of_particle(atom)) particles = bond_topology[bonds].flatten() other_particles = particles[particles != atom] other_types = np.sort(atom_types[other_particles]) key = tuple(other_types) if position_mask[atom]: if key not in self._data["structure_counts"][atom_type]: self._data["structure_counts"][atom_type][key] = 0 self._data["structure_counts"][atom_type][key] += 1 self._data["num_frames"] += 1
[docs] def join_samplers(self, num_cores): if self._process_id != -1: return combined_data = {} num_frames = 0 type_to_name = {v: k for k, v in self._atom_lib.items()} for process_id in range(num_cores) if num_cores > 1 else [-1]: file_path = self._name_out + f"_proc_{process_id}.pkl" proc_data = utils.load_object(file_path) os.remove(file_path) for identifier, data in proc_data.items(): if identifier == "input_params": combined_data["input_params"] = data elif identifier == "num_frames": num_frames += data elif identifier == "structure_counts": for key, value in data.items(): atom = type_to_name[key] if atom not in combined_data: combined_data[atom] = {} for structure, count in value.items(): name = atom + "+" + "_".join([type_to_name[t] for t in structure]) if name not in combined_data[atom]: combined_data[atom][name] = 0 combined_data[atom][name] += count for atom in combined_data: if atom != "input_params": combined_data[atom] = dict(sorted(combined_data[atom].items(), key=lambda item: item[1], reverse=True)) for structure in combined_data[atom]: combined_data[atom][structure] /= num_frames utils.save_object(combined_data, self._name_out + ".obj")