Saving ionic analyses#

Calculating, naming, persisting and querying formal-charge contacts.

Store the calculated analysis with its molecular system in H5MSM 0.5, then recover it through public conversion. The example uses explicit isolated-ion chemistry and constructed coordinates; it makes no force-field or energy claim.

Added in version 1.0.0.

Preparing the system#

The first structure has a contact; the second has none. Declare the completeness assumption explicitly for these isolated ions.

import numpy as np
import molsysmt as msm
from molsysmt import pyunitwizard as puw
from molsysmt.native import MolSys, Topology

molsys = MolSys()
molsys.topology = Topology(n_atoms=2)
molsys.topology.atoms['atom_type'] = ['Na', 'Cl']
msm.set(molsys, element='atom', formal_charge=[1, -1])
molsys.structures.append(coordinates=puw.quantity(np.array([
    [[0,0,0], [0.3,0,0]], [[0,0,0], [2,0,0]],
]), 'nm'))

Calculating and naming the analysis#

Check the result before attaching it. Mapping assignment validates that its atom and structure axes match the destination; it declares correspondence and does not authenticate molecular origin.

analysis = msm.interactions.ionic.get_ionic_interactions(
    molsys, '0.4 nm', pbc=False, assume_complete_connectivity=True)
assert not molsys.interactions
molsys.interactions = {**molsys.interactions, 'ionic': analysis}
print(analysis.evaluated_structure_indices.tolist())
print(analysis.measure_units)
print(analysis.software)
[0, 1]
{'distance': 'nm', 'positive_charge': 'e', 'negative_charge': 'e'}
{'molsysmt': '0.21.0+606.ga03eb4bf6'}

Saving and recovering#

The temporary file below contains both coordinates and the named analysis. H5MSM preserves the version that produced the observations, even if the version used to load it later differs. Recalculating from the same H5MSM path with atom-index selections and forced blocks reads chemistry and selected coordinates without loading the named analysis. The detector returns an independent result for comparison; it does not replace the saved analysis. Complete results still require RAM.

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    filename = str(Path(directory) / 'ionic.h5msm')
    msm.convert(molsys, to_form=filename)
    restored = msm.convert(filename, to_form='molsysmt.MolSys')

    with msm.configure.context(chunk_size=1):
        recomputed = msm.interactions.ionic.get_ionic_interactions(
            filename, '0.4 nm', pbc=False, assume_complete_connectivity=True,
            heavy_mode='force')
    np.testing.assert_array_equal(recomputed.occurrence_structures, analysis.occurrence_structures)
    np.testing.assert_allclose(recomputed.measurements['distance'], analysis.measurements['distance'])
    print(recomputed.execution_records[0]['details']['execution'], recomputed.execution_records[0]['details']['execution_chunks'])

saved = restored.interactions['ionic']
assert saved.software == analysis.software
assert saved.parameters == analysis.parameters
np.testing.assert_allclose(saved.measurements['distance'], [0.3])
print(list(restored.interactions))
chunked 2
['ionic']

Validation scope#

The bounded method has independent controls using the bundled Trp-cage (38 models) and villin HP35 coordinates with explicitly declared formal-charge states. Whole centers and their minimum distances are checked independently; the tests also cover empty frames, atom/frame selections, periodic image reconstruction, and H5MSM persistence. These controls validate the stated geometric rule for those states, not experimental protonation, interaction energies, or universal chemical recognition. The API remains experimental.

Querying a frame or atom#

The empty frame is evaluated, while occurrence indices identify the observations within this version of the analysis. Querying a saved result requires no new scientific calculation.

visible = saved.query(structure_indices=[0], atom_indices=[0]).to_dict()
empty = saved.query(structure_indices=[1]).to_dict()
print(visible['occurrence_indices'].tolist())
print(empty['evaluated_structure_indices'].tolist())
assert empty['occurrence_indices'].size == 0
assert empty['evaluated_structure_indices'].tolist() == [1]
[0]
[1]