mrsimulator I/O

We offer a range of serialization options based on a JSON structure demonstrated below.

Dictionary Representation of Objects

All mrsimulator objects can be serialized into a JSON format. Calling the json() method on an object will return a Python dictionary representing the object in JSON format. Below we call the json() method of the Site class.

from mrsimulator import Site, SpinSystem
from mrsimulator.spin_system.tensors import SymmetricTensor

Si29_site = Site(
    isotope="29Si",
    isotropic_chemical_shift=-89.0,
    shielding_symmetric=SymmetricTensor(
        zeta=59.8,
        eta=0.62,
    ),
)

py_dict = Si29_site.json()
print(py_dict)
# {
#     'isotope': '29Si',
#     'isotropic_chemical_shift': '-89.0 ppm',
#     'shielding_symmetric': {'zeta': '59.8 ppm', 'eta': 0.62}
# }

All values are serialized with units when applicable, but you may call json(units=False) if you wish to serialize values without units.

Similarly, all mrsimulator objects can be loaded from a dictionary representation. Here we construct the same site as a dictionary and call parse_dict_with_units() to create a Site object from a dictionary.

site_dict = {
    "isotope": "29Si",
    "isotropic_chemical_shift": "-89.0 ppm",
    "shielding_symmetric": {
        "zeta": "59.8 ppm",
        "eta": 0.62,
    },
}

Si29_site_from_dict = Site().parse_dict_with_units(site_dict)
print(Si29_site_from_dict == Si29_site)
# True

We see that both these sites are equivalent. Values in dictionaries can be given as a number and a unit in a string. However, passing values with units increases overhead and will throw errors if the units cannot be converted into the expected units for a field. For this reason, we recommend instantiating objects directly from classes.

Saving and Loading Spin Systems from a File

A list of spin systems in a Simulator object can be serialized to a file. Here we create a simulator with three distinct \(^{29}\text{Si}\) spin systems and serialize these spin systems to a file by calling export_spin_systems().

from mrsimulator import Site, SpinSystem, Simulator
from mrsimulator.spin_system.tensors import SymmetricTensor

# Create the spin systems
Si29_1 = SpinSystem(
    sites=[
        Site(
            isotope="29Si",
            isotropic_chemical_shift=-89.0,
            shielding_symmetric=SymmetricTensor(zeta=59.8, eta=0.62),
        )
    ]
)
Si29_2 = SpinSystem(
    sites=[
        Site(
            isotope="29Si",
            isotropic_chemical_shift=-89.5,
            shielding_symmetric=SymmetricTensor(zeta=52.1, eta=0.68),
        )
    ]
)
Si29_3 = SpinSystem(
    sites=[
        Site(
            isotope="29Si",
            isotropic_chemical_shift=-87.8,
            shielding_symmetric=SymmetricTensor(zeta=69.4, eta=0.60),
        )
    ]
)

# Create the Simulator object
sim = Simulator(spin_systems=[Si29_1, Si29_2, Si29_3])

# Save spin systems to file
sim.export_spin_systems("example.mrsys")

Now the file example.mrsys holds a JSON representation of the spin system objects. We encourage the convention of using .mrsys extension for this JSON file.

Just as spin systems can be saved to a file, spin systems can be loaded from a file. Loading spin systems is useful when working with a large number of spin systems over multiple Python scripts. Here we load the spin system file, example.mrsys, into a new simulator using the method load_spin_systems().

new_sim = Simulator()
new_sim.load_spin_systems("example.mrsys")
print(len(new_sim.spin_systems))
# 3

Saving and Loading Methods from a File

A list of methods in a Simulator object can be serialized to a file. Here we create a custom DAS method and serialize it to a file using the method export_methods().

from mrsimulator import Simulator
from mrsimulator.method import Method
from mrsimulator.method import SpectralDimension, SpectralEvent

# Create DAS method
das = Method(
    name="DAS of 17O",
    channels=["17O"],
    magnetic_flux_density=11.744,
    spectral_dimensions=[
        SpectralDimension(
            count=512,
            spectral_width=10000,
            reference_offset=-1220.9,
            origin_offset=67793215,
            label="Isotropic dimension",
            events=[
                SpectralEvent(
                    fraction=0.5,
                    rotor_angle=37.38 * 3.14159 / 180,
                    transition_queries=[{"ch1": {"P": [-1], "D": [0]}}],
                ),
                SpectralEvent(
                    fraction=0.5,
                    rotor_angle=79.19 * 3.14159 / 180,
                    transition_queries=[{"ch1": {"P": [-1], "D": [0]}}],
                ),
            ],
        ),
        # The last spectral dimension block is the direct-dimension
        SpectralDimension(
            count=256,
            spectral_width=11001,
            reference_offset=-1228,
            origin_offset=67793215,
            label="MAS dimension",
            events=[
                SpectralEvent(
                    rotor_angle=54.735 * 3.14159 / 180,
                    transition_queries=[{"ch1": {"P": [-1], "D": [0]}}],
                )
            ],
        ),
    ],
)

# Create simulator with das method
sim = Simulator(methods=[das])

# Save methods to file
sim.export_methods("example.mrmtd")

Now the file example.mrmtd holds a JSON representation of the method object. If multiple methods are present, e.g., at different spinning speeds, they will also be serialized. We encourage the convention of using .mrmtd extension for this JSON file.

Just like spin systems, methods can also be loaded from a file. Here we load the DAS method into a new simulator object by calling the method load_methods().

new_sim = Simulator()
new_sim.load_methods("example.mrmtd")
print(new_sim.methods[0].name)
# DAS of 17O

Loading complex methods from a file, like the DAS example above, can reduce complex code. Methods representing actual experiments can be saved to a file to later be loaded into a script as needed.

Serializing a Simulator Object

The entire Simulator object may be serialized to a JSON-compliant file using the save() Python method. By default, the attribute values are serialized as physical quantities represented as a string with a value and a unit.

sim = Simulator()
# ... Setup Simulator object
sim.save("sample.mrsim")

Now the file sample.mrsim holds the JSON representation of sim, a Simulator object. To load a simulator from a file, call the class method load(). By default, the load method parses the file for units.

new_sim = Simulator.load("sample.mrsim")

Serialize simulation from a Method to a CSDM Compliant File

The simulated spectrum may be exported to a CSDM-compliant JSON file using the following code:

sim_coesite.methods[0].simulation.save("coesite_simulation.csdf")

For more information on the CSDM format, see the csdmpy documentation.

Serialize Simulator and SignalProcessor object

The Simulator object and a list of Signal Processor objects can both be serialized within the same file by calling the save() method.

from mrsimulator import save
from mrsimulator import Simulator
from mrsimulator import signal_processor as sp

sim = Simulator()
processor1 = sp.SignalProcessor()
processor2 = sp.SignalProcessor()

save(
    filename="example.mrsim",
    simulator=sim,
    signal_processors=[processor1, processor2],
)

By default, all attribute values are serialized with units. You can serialize attributes without units, assuming the default unit of the attribute, by passing with_units=False to the method. Recall that all objects in mrsimulator have the attribute property_units which provides the default units for all class properties. Additionally, a metadata dictionary can be passed using the application keyword.

To load a simulator and signal processors from a file, call the load() method. This method will return an ordered list of a Simulator object, a list of Signal Processor objects, and a metadata dictionary

from mrsimulator import load

sim, processors, application = load("example.mrsim")

Note

The serialization structure has been updated in mrsimulator v0.7. Any .mrsim files from v0.6 and earlier will not work. See Changelog for breaking changes.