Source code for porereax.utils
"""
Module providing utility functions for the PoreReax package.
This module includes functions for saving and loading Python objects using
pickle, loading YAML configuration files, and common mathematical operations
such as the minimum-image convention.
"""
import pickle
import yaml
import numpy as np
import os
[docs]
def save_object(obj, filename):
"""
Save a Python object to a file using pickle.
Parameters
----------
obj : any
The Python object to be saved.
filename : str
The path to the file where the object will be saved.
"""
with open(filename, 'wb') as f:
pickle.dump(obj, f)
[docs]
def load_object(file_path):
"""
Load a Python object from a file using pickle.
Parameters
----------
file_path : str
The path to the file from which the object will be loaded.
Returns
-------
any
The loaded Python object.
"""
file_path = os.path.abspath(file_path)
if not os.path.exists(file_path):
raise FileNotFoundError(f"The file {file_path} does not exist.")
with open(file_path, 'rb') as f:
return pickle.load(f)
[docs]
def load_yaml(file_path: str) -> dict:
"""
Load a YAML file and return its contents as a dictionary.
Parameters
----------
file_path : str
The path to the YAML file.
Returns
-------
dict
The contents of the YAML file as a dictionary.
"""
file_path = os.path.abspath(file_path)
if not os.path.exists(file_path):
raise FileNotFoundError(f"The file {file_path} does not exist.")
with open(file_path, 'r') as f:
data = yaml.safe_load(f)
return data
[docs]
def min_image_convention(vec: np.ndarray, box: np.ndarray) -> np.ndarray:
"""
Apply the minimal image convention to a vector given the simulation box dimensions.
Parameters
----------
vec : np.ndarray
The input vector (shape: (N, 3)).
box : np.ndarray
The simulation box dimensions (shape: (3,)).
Returns
-------
np.ndarray
The vector adjusted by the minimal image convention (shape: (N, 3)).
"""
return vec - box * np.round(vec / box)
[docs]
def min_image_midpoint(vec1: np.ndarray, vec2: np.ndarray, box: np.ndarray) -> np.ndarray:
"""
Calculate the midpoint between two vectors considering the minimal image convention.
Parameters
----------
vec1 : np.ndarray
The first vector (shape: (N, 3)).
vec2 : np.ndarray
The second vector (shape: (N, 3)).
box : np.ndarray
The simulation box dimensions (shape: (3,)).
Returns
-------
np.ndarray
The midpoint vector adjusted by the minimal image convention (shape: (N, 3)).
"""
dist = vec2 - vec1
dist = min_image_convention(dist, box)
midpoint = vec1 + 0.5 * dist
midpoint %= box
return midpoint
[docs]
def get_identifiers(link_data: str) -> list:
"""
Retrieve the list of identifiers from a data file.
Parameters
----------
link_data : str
Path to the data file created by a sampler instance.
Returns
-------
list
List of identifiers present in the data file.
"""
data = load_object(link_data)
return [identifier for identifier in data.keys() if identifier != "input_params" and identifier != "num_frames"]
[docs]
def get_data(link_data: str, identifier: str) -> dict:
"""
Retrieve the data for a specific identifier from a data file.
Parameters
----------
link_data : str
Path to the data file created by a sampler instance.
identifier : str
The identifier for which to retrieve the data.
Returns
-------
dict
The data corresponding to the specified identifier.
"""
data = load_object(link_data)
if identifier not in data:
raise ValueError(f"Identifier '{identifier}' not found in the data file.")
return data[identifier]
[docs]
def read_pore_yml(file_path: str) -> dict:
"""
Read a YAML file containing pore system properties and extract relevant depending on the pore shape.
Parameters
----------
file_path : str
Path to the YAML file containing pore system properties.
Returns
-------
dict
A dictionary containing the extracted pore properties.
"""
properties = {}
system_data = load_yaml(file_path)
if len(system_data) > 2:
raise NotImplementedError("Only systems with one pore are supported.")
reservoir = system_data["system"]["reservoir"]
properties["reservoir"] = reservoir * 10
if system_data["shape_00"]["shape"] == "CYLINDER":
if system_data["shape_00"]["parameter"]["central"] != [0, 0, 1]:
raise NotImplementedError("Only CYLINDER pores with central axis along z (0,0,1) are supported.")
pore_length = 2 * system_data["system"]["centroid"][2] * 10
box_length = system_data["system"]["dimensions"][2] * 10
center = np.array(system_data["shape_00"]["parameter"]["centroid"]) * 10
center[2] = box_length / 2
gap = (box_length - pore_length - 2 * reservoir) / 2
pore_range = np.array([reservoir + gap, box_length - reservoir - gap])
properties["type"] = "cylinder"
properties["radius"] = system_data["shape_00"]["diameter"] / 2 * 10
properties["length"] = pore_length
properties["center"] = center
properties["range"] = pore_range
else:
raise NotImplementedError("Currently, only CYLINDER pores are supported.")
return properties