def setup_simulator(): site = Site( isotope="23Na", isotropic_chemical_shift=32, shielding_symmetric={ "zeta": -120, "eta": 0.1 }, quadrupolar={ "Cq": 1e5, "eta": 0.31, "beta": 5.12 }, ) sys = SpinSystem(sites=[site], abundance=0.123) sim = Simulator() sim.spin_systems.append(sys) sim.methods.append( BlochDecayCTSpectrum(channels=["2H"], rotor_frequency=1e3)) sim.methods.append( BlochDecaySpectrum(channels=["2H"], rotor_frequency=12.5e3)) sim.methods.append(ThreeQ_VAS(channels=["27Al"])) sim.methods.append(SSB2D(channels=["17O"], rotor_frequency=35500)) return sim
"eta": 0.1 }, ) sn117 = Site( isotope="117Sn", isotropic_chemical_shift=0, ) j_sn = Coupling( site_index=[0, 1], isotropic_j=8150.0, ) sn117_abundance = 7.68 # in % spin_systems = [ # uncoupled spin system SpinSystem(sites=[sn119], abundance=100 - sn117_abundance), # coupled spin systems SpinSystem(sites=[sn119, sn117], couplings=[j_sn], abundance=sn117_abundance), ] # %% # **Method** # Get the spectral dimension parameters from the experiment. spectral_dims = get_spectral_dimensions(experiment) MAS = BlochDecaySpectrum( channels=["119Sn"], magnetic_flux_density=9.395, # in T
ax.plot(experiment, "k", alpha=0.5) ax.set_xlim(100, -100) plt.grid() plt.tight_layout() plt.show() # %% # Create a fitting model # ---------------------- # **Spin System** B11 = Site( isotope="11B", isotropic_chemical_shift=20.0, # in ppm quadrupolar=SymmetricTensor(Cq=2.3e6, eta=0.03), # Cq in Hz ) spin_systems = [SpinSystem(sites=[B11])] # %% # **Method** # Get the spectral dimension parameters from the experiment. spectral_dims = get_spectral_dimensions(experiment) MAS_CT = BlochDecayCTSpectrum( channels=["11B"], magnetic_flux_density=14.1, # in T rotor_frequency=12500, # in Hz spectral_dimensions=spectral_dims, experiment=experiment, # add the measurement to the method. )
def SSB2D_setup(ist, vr, method_type): sites = [ Site( isotope=ist, isotropic_chemical_shift=29, shielding_symmetric={ "zeta": -70, "eta": 0.000 }, ), Site( isotope=ist, isotropic_chemical_shift=44, shielding_symmetric={ "zeta": -96, "eta": 0.166 }, ), Site( isotope=ist, isotropic_chemical_shift=57, shielding_symmetric={ "zeta": -120, "eta": 0.168 }, ), ] spin_systems = [SpinSystem(sites=[s]) for s in sites] B0 = 11.7 if method_type == "PASS": method = SSB2D( channels=[ist], magnetic_flux_density=B0, # in T rotor_frequency=vr, spectral_dimensions=[ { "count": 32, "spectral_width": 32 * vr, # in Hz "label": "Anisotropic dimension", }, # The last spectral dimension block is the direct-dimension { "count": 2048, "spectral_width": 2e4, # in Hz "reference_offset": 5e3, # in Hz "label": "Fast MAS dimension", }, ], ) else: method = Method2D( channels=[ist], magnetic_flux_density=B0, # in T spectral_dimensions=[ { "count": 64, "spectral_width": 8e4, # in Hz "label": "Anisotropic dimension", "events": [{ "rotor_angle": 90 * 3.14159 / 180 }], }, # The last spectral dimension block is the direct-dimension { "count": 2048, "spectral_width": 2e4, # in Hz "reference_offset": 5e3, # in Hz "label": "Fast MAS dimension", }, ], affine_matrix=[[1, -1], [0, 1]], ) sim = Simulator() sim.spin_systems = spin_systems # add spin systems sim.methods = [method] # add the method. sim.run() data_ssb = sim.methods[0].simulation dim_ssb = data_ssb.x[0].coordinates.value if method_type == "PASS": bloch = BlochDecaySpectrum( channels=[ist], magnetic_flux_density=B0, # in T rotor_frequency=vr, # in Hz spectral_dimensions=[ { "count": 32, "spectral_width": 32 * vr, # in Hz "reference_offset": 0, # in Hz "label": "MAS dimension", }, ], ) else: bloch = BlochDecaySpectrum( channels=[ist], magnetic_flux_density=B0, # in T rotor_frequency=vr, # in Hz rotor_angle=90 * 3.14159 / 180, spectral_dimensions=[ { "count": 64, "spectral_width": 8e4, # in Hz "reference_offset": 0, # in Hz "label": "MAS dimension", }, ], ) for i in range(3): iso = spin_systems[i].sites[0].isotropic_chemical_shift sys = spin_systems[i].copy() sys.sites[0].isotropic_chemical_shift = 0 sim2 = Simulator() sim2.spin_systems = [sys] # add spin systems sim2.methods = [bloch] # add the method. sim2.run() index = np.where(dim_ssb < iso)[0][-1] one_d_section = data_ssb.y[0].components[0][:, index] one_d_section /= one_d_section.max() one_d_sim = sim2.methods[0].simulation.y[0].components[0] one_d_sim /= one_d_sim.max() np.testing.assert_almost_equal(one_d_section, one_d_sim, decimal=6)
def test_MQMAS(): site = Site( isotope="87Rb", isotropic_chemical_shift=-9, shielding_symmetric={ "zeta": 100, "eta": 0 }, quadrupolar={ "Cq": 3.5e6, "eta": 0.36, "beta": 70 / 180 * np.pi }, ) spin_system = SpinSystem(sites=[site]) method = Method2D( channels=["87Rb"], magnetic_flux_density=9.4, spectral_dimensions=[ { "count": 128, "spectral_width": 20000, "events": [{ "transition_query": [{ "P": [-3], "D": [0] }] }], }, { "count": 128, "spectral_width": 20000, "events": [{ "transition_query": [{ "P": [-1], "D": [0] }] }], }, ], ) sim = Simulator() sim.spin_systems = [spin_system] sim.methods = [method] sim.config.integration_volume = "hemisphere" sim.run() # process k = 21 / 27 # shear factor processor = sp.SignalProcessor(operations=[ sp.IFFT(dim_index=1), aft.Shear(factor=-k, dim_index=1, parallel=0), aft.Scale(factor=1 + k, dim_index=1), sp.FFT(dim_index=1), ]) processed_data = processor.apply_operations( data=sim.methods[0].simulation).real # Since there is a single site, after the shear and scaling transformations, there # should be a single perak along the isotropic dimension at index 70. # The isotropic coordinate of this peak is given by # w_iso = (17.8)*iso_shift + 1e6/8 * (vq/v0)^2 * (eta^2 / 3 + 1) # ref: D. Massiot et al. / Solid State Nuclear Magnetic Resonance 6 (1996) 73-83 iso_slice = processed_data[40, :] assert np.argmax(iso_slice.y[0].components[0]) == 70 # calculate the isotropic coordinate spin = method.channels[0].spin w0 = method.channels[0].gyromagnetic_ratio * 9.4 * 1e6 wq = 3 * 3.5e6 / (2 * spin * (2 * spin - 1)) w_iso = -9 * 17 / 8 + 1e6 / 8 * (wq / w0)**2 * ((0.36**2) / 3 + 1) # the coordinate from spectrum w_iso_spectrum = processed_data.x[1].coordinates[70].value np.testing.assert_almost_equal(w_iso, w_iso_spectrum, decimal=2) # The projection onto the MAS dimension should be the 1D block decay central # transition spectrum mas_slice = processed_data.sum(axis=1).y[0].components[0] # MAS spectrum method = BlochDecayCTSpectrum( channels=["87Rb"], magnetic_flux_density=9.4, rotor_frequency=1e9, spectral_dimensions=[{ "count": 128, "spectral_width": 20000 }], ) sim = Simulator() sim.spin_systems = [spin_system] sim.methods = [method] sim.config.integration_volume = "hemisphere" sim.run() data = sim.methods[0].simulation.y[0].components[0] np.testing.assert_almost_equal(data / data.max(), mas_slice / mas_slice.max(), decimal=2, err_msg="not equal")
# %% # Generate the site and spin system objects. sites = [ Site( isotope="87Rb", isotropic_chemical_shift=16, # in ppm quadrupolar=SymmetricTensor(Cq=5.3e6, eta=0.1), # Cq in Hz ), Site( isotope="87Rb", isotropic_chemical_shift=40, # in ppm quadrupolar=SymmetricTensor(Cq=2.6e6, eta=1.0), # Cq in Hz ), ] spin_systems = [SpinSystem(sites=[s]) for s in sites] # %% # Use the ``SSB2D`` method to simulate a PASS, MAT, QPASS, QMAT, or any equivalent # sideband separation spectrum. Here, we use the method to generate a QMAT spectrum. # The QMAT method is created from the ``SSB2D`` method in the same as a PASS or MAT # method. The difference is that the observed channel is a half-integer quadrupolar # spin instead of a spin I=1/2. qmat = SSB2D( channels=["87Rb"], magnetic_flux_density=9.4, rotor_frequency=2604, spectral_dimensions=[ dict( count=32 * 4, spectral_width=2604 * 32, # in Hz
def test_2D(): site_Ni = Site( isotope="2H", isotropic_chemical_shift=-97, # in ppm shielding_symmetric=dict( zeta=-551, eta=0.12, alpha=62 * np.pi / 180, beta=114 * np.pi / 180, gamma=171 * np.pi / 180, ), quadrupolar=dict(Cq=77.2e3, eta=0.9), # Cq in Hz ) spin_system = SpinSystem(sites=[site_Ni]) data = [] for angle, n_gamma in zip([0, np.pi / 4], [1, 500]): shifting_d = Method( name="Shifting-d", channels=["2H"], magnetic_flux_density=9.395, # in T rotor_frequency=0, # in Hz rotor_angle=angle, # in Hz spectral_dimensions=[ SpectralDimension( count=512, spectral_width=2.5e5, # in Hz label="Quadrupolar frequency", events=[ SpectralEvent( transition_queries=[{ "ch1": { "P": [-1] } }], freq_contrib=["Quad1_2"], ), MixingEvent(query="NoMixing"), ], ), SpectralDimension( count=256, spectral_width=2e5, # in Hz reference_offset=2e4, # in Hz label="Paramagnetic shift", events=[ SpectralEvent( transition_queries=[{ "ch1": { "P": [-1] } }], freq_contrib=["Shielding1_0", "Shielding1_2"], ) ], ), ], ) sim = Simulator(spin_systems=[spin_system], methods=[shifting_d]) sim.config.integration_volume = "hemisphere" sim.config.number_of_gamma_angles = n_gamma sim.run(auto_switch=False) res = sim.methods[0].simulation.y[0].components[0] data.append(res / res.max()) # _, ax = plt.subplots(1, 2) # ax[0].imshow(data[0].real) # ax[1].imshow(data[1].real) # plt.show() np.testing.assert_almost_equal(data[0], data[1], decimal=1.8)
def parse_dict_with_units(cls, py_dict: dict): """Parse the physical quantity from a dictionary representation of the Simulator object, where the physical quantity is expressed as a string with a number and a unit. Args: dict py_dict: A required python dict object. Returns: A :ref:`simulator_api` object. Example ------- >>> sim_py_dict = { ... 'config': { ... 'decompose_spectrum': 'none', ... 'integration_density': 70, ... 'integration_volume': 'octant', ... 'number_of_sidebands': 64 ... }, ... 'spin_systems': [ ... { ... 'abundance': '100 %', ... 'sites': [{ ... 'isotope': '13C', ... 'isotropic_chemical_shift': '20.0 ppm', ... 'shielding_symmetric': {'eta': 0.5, 'zeta': '10.0 ppm'} ... }] ... }, ... { ... 'abundance': '100 %', ... 'sites': [{ ... 'isotope': '1H', ... 'isotropic_chemical_shift': '-4.0 ppm', ... 'shielding_symmetric': {'eta': 0.1, 'zeta': '2.1 ppm'} ... }] ... }, ... { ... 'abundance': '100 %', ... 'sites': [{ ... 'isotope': '27Al', ... 'isotropic_chemical_shift': '120.0 ppm', ... 'shielding_symmetric': {'eta': 0.1, 'zeta': '2.1 ppm'} ... }] ... } ... ] ... } >>> sim = Simulator.parse_dict_with_units(sim_py_dict) >>> len(sim.spin_systems) 3 """ py_copy_dict = deepcopy(py_dict) if "spin_systems" in py_copy_dict: spin_sys = py_copy_dict["spin_systems"] spin_sys = [SpinSystem.parse_dict_with_units(obj) for obj in spin_sys] py_copy_dict["spin_systems"] = spin_sys if "methods" in py_copy_dict: methods = py_copy_dict["methods"] methods = [Method.parse_dict_with_units(obj) for obj in methods] py_copy_dict["methods"] = methods return Simulator(**py_copy_dict)
# Create a fitting model # ---------------------- # **Guess model** # # Create a guess list of spin systems. For fitting the sideband profile at an isotropic # chemical shift cross-section from PASS/MAT datasets, set the isotropic_chemical_shift # parameter of the site object as zero. site = Site( isotope="13C", isotropic_chemical_shift=0, # shielding_symmetric={ "zeta": -70, "eta": 0.8 }, ) spin_systems = [SpinSystem(sites=[site])] # %% # **Method** # # For the sideband-only cross-section, use the BlochDecaySpectrum method. # Get the dimension information from the experiment. spectral_dims = get_spectral_dimensions(pass_cross_section) PASS = BlochDecaySpectrum( channels=["13C"], magnetic_flux_density=9.395, # in T rotor_frequency=1500, # in Hz spectral_dimensions=spectral_dims, experiment=pass_cross_section, # also add the measurement to the method.
Si29_2 = Site( isotope="29Si", isotropic_chemical_shift=-89.5, # in ppm shielding_symmetric=SymmetricTensor(zeta=52.1, eta=0.68), # zeta in ppm ) Si29_3 = Site( isotope="29Si", isotropic_chemical_shift=-87.8, # in ppm shielding_symmetric=SymmetricTensor(zeta=69.4, eta=0.60), # zeta in ppm ) # %% # **Step 2:** Create the spin systems from these sites. Again, we create three # single-site spin systems for better performance. spin_systems = [ SpinSystem(sites=[Si29_1]), SpinSystem(sites=[Si29_2]), SpinSystem(sites=[Si29_3]), ] # %% # **Step 3:** Create a Bloch decay spectrum method. method = BlochDecaySpectrum( channels=["29Si"], magnetic_flux_density=14.1, # in T rotor_frequency=1500, # in Hz spectral_dimensions=[ dict( count=2048, spectral_width=25000, # in Hz reference_offset=-10000, # in Hz
from mrsimulator import methods as NamedMethods from mrsimulator import Simulator from mrsimulator import SpinSystem from mrsimulator.method import Method from mrsimulator.methods import Method1D from mrsimulator.methods import Method2D from mrsimulator.utils.error import ImmutableEventError __author__ = "Deepansh J. Srivastava" __email__ = "*****@*****.**" methods = [ val for k, val in NamedMethods.__dict__.items() if isinstance(val, type) ] sys = SpinSystem(sites=[{"isotope": "1H"}]) def test_read_write_methods(): def assert_parsing(method, fn1): fn2 = method.parse_dict_with_units(fn1.json()) assert fn1 == fn2, f"Error with {method} parse with units." fn3 = method(**fn1.json(units=False)) assert fn1 == fn3, f"Error with {method} parse with units." event_error = "Event objects are immutable for" serialize = fn1.json() ent = serialize["spectral_dimensions"][0]["events"] ent[0]["transition_query"][0]["ch1"]["P"] = [-100]
def single_site_system_generator( isotope: Union[str, List[str]], isotropic_chemical_shift: Union[float, List[float], np.ndarray] = 0, shielding_symmetric: Dict = None, shielding_antisymmetric: Dict = None, quadrupolar: Dict = None, abundance: Union[float, List[float], np.ndarray] = None, site_name: Union[str, List[str]] = None, site_label: Union[str, List[str]] = None, site_description: Union[str, List[str]] = None, rtol: float = 1e-3, ) -> List[SpinSystem]: r"""Generate and return a list of single-site spin systems from the input parameters Args: isotope: A required string or a list of site isotopes. isotropic_chemical_shift: A float or a list/ndarray of isotropic chemical shifts per site per spin system. The default is 0. shielding_symmetric: A shielding symmetric dict object, where the keyword value can either be a float or a list/ndarray of floats. The default value is None. The allowed keywords are ``zeta``, ``eta``, ``alpha``, ``beta``, and ``gamma``. shielding_antisymmetric: A shielding antisymmetric dict object, where the keyword value can either be a float or a list/ndarray of floats. The default value is None. The allowed keywords are ``zeta``, ``alpha``, and ``beta``. quadrupolar: A quadrupolar dict object, where the keyword value can either be a float or a list/ndarray of floats. The default value is None. The allowed keywords are ``Cq``, ``eta``, ``alpha``, ``beta``, and ``gamma``. abundance: A float or a list/ndarray of floats describing the abundance of each spin system. site_name: A string or a list of strings with site names per site per spin system. The default is None. site_label: A string or a list of strings with site labels per site per spin system. The default is None. site_description: A string or a list of strings with site descriptions per site per spin system. The default is None. rtol: The relative tolerance used in determining the cutoff abundance, given as, :math:`\tt{abundance}_{\tt{cutoff}} = \tt{rtol} * \tt{max(abundance)}.` The spin systems with abundance below this threshold are ignored. Returns: List of :ref:`spin_sys_api` objects with a single :ref:`site_api` Example: **Single spin system:** >>> sys1 = single_site_system_generator( ... isotope=["1H"], ... isotropic_chemical_shift=10, ... site_name="Single Proton", ... ) >>> print(len(sys1)) 1 **Multiple spin system:** >>> sys2 = single_site_system_generator( ... isotope="1H", ... isotropic_chemical_shift=[10] * 5, ... site_name="5 Protons", ... ) >>> print(len(sys2)) 5 **Multiple spin system with dictionary arguments:** >>> Cq = [4.2e6] * 12 >>> sys3 = single_site_system_generator( ... isotope="17O", ... isotropic_chemical_shift=60.0, # in ppm, ... quadrupolar={"Cq": Cq, "eta": 0.5}, # Cq in Hz ... ) >>> print(len(sys3)) 12 .. note:: The parameter value can either be a float or a list/ndarray. If the parameter value is a float, the given value is assigned to the respective parameter in all the spin systems. If the parameter value is a list or ndarray, its `ith` value is assigned to the respective parameter of the `ith` spin system. When multiple parameter values are given as lists/ndarrays, the length of all the lists must be the same. """ isotope = _flatten_item(isotope) isotropic_chemical_shift = _flatten_item(isotropic_chemical_shift) shielding_symmetric = _flatten_item(shielding_symmetric) shielding_antisymmetric = _flatten_item(shielding_antisymmetric) quadrupolar = _flatten_item(quadrupolar) site_name = _flatten_item(site_name) site_label = _flatten_item(site_label) site_description = _flatten_item(site_description) abundance = _flatten_item(abundance) args = [ isotope, isotropic_chemical_shift, shielding_symmetric, shielding_antisymmetric, quadrupolar, site_name, site_label, site_description, abundance, ] n_sites = _check_lengths_of_args(*args) sites = _site_generator(n_sites, *args[:-1]) # Don't pass abundance if abundance is None: abundance = np.asarray([1 / n_sites] * n_sites) if isinstance(abundance, (int, float, np.floating)): abundance = np.asarray([abundance] * n_sites) keep_idxs = np.asarray(abundance > rtol * abundance.max()).nonzero()[0] return [ SpinSystem(sites=[site], abundance=abd) for i, (site, abd) in enumerate(zip(sites, abundance)) if i in keep_idxs ]
ax.set_xlim(600, -700) plt.grid() plt.tight_layout() plt.show() # %% # Create a fitting model # ---------------------- # **Spin System** H_2 = Site( isotope="2H", isotropic_chemical_shift=-57.12, # in ppm, quadrupolar=SymmetricTensor(Cq=3e4, eta=0.0), # Cq in Hz ) spin_systems = [SpinSystem(sites=[H_2])] # %% # **Method** # Get the spectral dimension parameters from the experiment. spectral_dims = get_spectral_dimensions(experiment) MAS = BlochDecaySpectrum( channels=["2H"], magnetic_flux_density=9.395, # in T rotor_frequency=4517.1, # in Hz spectral_dimensions=spectral_dims, experiment=experiment, # experimental dataset )
def test_warnings(): s = SpinSystem(sites=[Site(isotope="23Na")]) m = Method(channels=["1H"], spectral_dimensions=[{}]) assert m.get_transition_pathways(s) == []
# **Spin System**: The objective of a multi-data fitting is to optimize the spin # system parameters using multiple datasets. In this example, we create two single-site # spin systems, which are then shared by three method objects. C1 = Site( isotope="13C", isotropic_chemical_shift=176.0, # in ppm shielding_symmetric=SymmetricTensor(zeta=60, eta=0.6), # zeta in Hz ) C2 = Site( isotope="13C", isotropic_chemical_shift=43.0, # in ppm shielding_symmetric=SymmetricTensor(zeta=30, eta=0.5), # zeta in Hz ) spin_systems = [ SpinSystem(sites=[C1], name="C1"), SpinSystem(sites=[C2], name="C2") ] # %% # **Method**: Create the three MAS method objects with respective MAS spinning speeds. # Get the spectral dimension parameters from the respective experiment and setup the # corresponding method. # Method for dataset 1 spectral_dims1 = get_spectral_dimensions(experiment1) MAS1 = BlochDecaySpectrum( channels=["13C"], magnetic_flux_density=7.05, # in T rotor_frequency=5000, # in Hz
def test_bad_assignments(): error = "value is not a valid list" with pytest.raises(ValidationError, match=f".*{error}.*"): SpinSystem(sites=Site())
{"count": 2048, "spectral_width": "25 kHz", "reference_offset": "0 Hz",} ], } method2 = { "channels": ["1H"], "magnetic_flux_density": "9.4 T", "rotor_frequency": "1 kHz", "rotor_angle": "54.735 deg", "spectral_dimensions": [ {"count": 2048, "spectral_width": "25 kHz", "reference_offset": "0 Hz",} ], } sim = Simulator() sim.spin_systems = [SpinSystem.parse_dict_with_units(item) for item in spin_systems] sim.methods = [ BlochDecaySpectrum.parse_dict_with_units(method1), BlochDecaySpectrum.parse_dict_with_units(method2), ] sim.run() freq1, amp1 = sim.methods[0].simulation.to_list() freq2, amp2 = sim.methods[1].simulation.to_list() fig, ax = plt.subplots(1, 2, figsize=(6, 3)) ax[0].plot(freq1, amp1, linewidth=1.0, color="k") ax[0].set_xlabel(f"frequency ratio / {freq2.unit}") ax[0].grid(color="gray", linestyle="--", linewidth=0.5, alpha=0.5) ax[0].set_title("Static")
# %% # For demonstration, we will create two spin systems, one with a single site and other # with two spin 1/2 sites. S1 = Site( isotope="1H", isotropic_chemical_shift=10, # in ppm shielding_symmetric=SymmetricTensor(zeta=-80, eta=0.25), # zeta in ppm ) S2 = Site(isotope="1H", isotropic_chemical_shift=-10) S12 = Coupling(site_index=[0, 1], isotropic_j=100, dipolar=SymmetricTensor(D=2000, eta=0, alpha=0)) spin_system_1 = SpinSystem(sites=[S1], label="Uncoupled system") spin_system_2 = SpinSystem(sites=[S1, S2], couplings=[S12], label="Coupled system") # %% # **Create a custom method** # # Writing a custom method is simply specifying an appropriate list of event objects per # spectral dimension. In this example, we are interested in a one-dimensional Hahnecho # method, and we use the generic `Method1D` class as a template. For a Hahnecho, we will # use two types of Event objects---SpectralEvent and MixingEvent. # # A SpectralEvent object is where we sample the frequency contributions. The net # frequency along a given spectral dimension is a weighted average of the frequencies # from all SpectralEvent objects within a given SpectralDimension, i.e.,
def test_csa_01(): site = Site(isotope="13C", shielding_symmetric={"zeta": 50, "eta": 0.5}) spin_system = SpinSystem(sites=[site]) setup_test(spin_system, volume="octant", sw=2.5e4)
import csdmpy as cp import numpy as np from mrsimulator import signal_processing as sp from mrsimulator import Simulator from mrsimulator import SpinSystem from mrsimulator.methods import BlochDecaySpectrum from .test_signal_processing import setup_read_write __author__ = "Maxwell C. Venetos" __email__ = "*****@*****.**" sim = Simulator() the_site = {"isotope": "1H", "isotropic_chemical_shift": "0 ppm"} the_spin_system = {"name": "site A", "sites": [the_site], "abundance": "80%"} spin_system_object = SpinSystem.parse_dict_with_units(the_spin_system) sim.spin_systems += [ spin_system_object, spin_system_object, spin_system_object ] sim.config.decompose_spectrum = "spin_system" sim.methods += [ BlochDecaySpectrum( channels=["1H"], magnetic_flux_density=9.4, spectral_dimensions=[{ "count": 65536, "spectral_width": 25000 }], ) ]
isotropic_chemical_shift=58, quadrupolar={ "Cq": 5.16e6, "eta": 0.292 }) # all five sites. sites = [O17_1, O17_2, O17_3, O17_4, O17_5] # %% # **Step 2:** Create the spin systems from these sites. For optimum performance, we # create five single-site spin systems instead of a single five-site spin system. The # abundance of each spin system is taken from above reference. abundance = [0.83, 1.05, 2.16, 2.05, 1.90] spin_systems = [ SpinSystem(sites=[s], abundance=a) for s, a in zip(sites, abundance) ] # %% # **Step 3:** Create a central transition selective Bloch decay spectrum method. method = BlochDecayCentralTransitionSpectrum( channels=["17O"], rotor_frequency=14000, # in Hz spectral_dimensions=[{ "count": 2048, "spectral_width": 50000, # in Hz "label": r"$^{17}$O resonances", }], ) # %%
ax.set_xlim(200, -200) plt.grid() plt.tight_layout() plt.show() # %% # Create a fitting model # ---------------------- # **Spin System** P_31 = Site( isotope="31P", isotropic_chemical_shift=5.0, # in ppm, shielding_symmetric=SymmetricTensor(zeta=-80, eta=0.5), # zeta in Hz ) spin_systems = [SpinSystem(sites=[P_31])] # %% # **Method** # Get the spectral dimension parameters from the experiment. spectral_dims = get_spectral_dimensions(experiment) static1D = BlochDecaySpectrum( channels=["31P"], magnetic_flux_density=9.395, # in T rotor_frequency=0, # in Hz spectral_dimensions=spectral_dims, experiment=experiment, # experimental dataset )
def test_ThreeQ_VAS_spin_3halves(): site = Site( isotope="87Rb", isotropic_chemical_shift=-9, shielding_symmetric={ "zeta": 100, "eta": 0 }, quadrupolar={ "Cq": 3.5e6, "eta": 0.36, "beta": 70 / 180 * np.pi }, ) spin_system = SpinSystem(sites=[site]) method = ThreeQ_VAS( channels=["87Rb"], magnetic_flux_density=9.4, spectral_dimensions=[ { "count": 1024, "spectral_width": 20000 }, { "count": 512, "spectral_width": 20000 }, ], ) sim = Simulator() sim.spin_systems = [spin_system] sim.methods = [method] sim.config.integration_volume = "hemisphere" sim.run() data = sim.methods[0].simulation dat = data.y[0].components[0] index = np.where(dat == dat.max())[0] # The isotropic coordinate of this peak is given by # v_iso = (17/8)*iso_shift + 1e6/8 * (vq/v0)^2 * (eta^2 / 3 + 1) # ref: D. Massiot et al. / Solid State Nuclear Magnetic Resonance 6 (1996) 73-83 spin = method.channels[0].spin v0 = method.channels[0].gyromagnetic_ratio * 9.4 * 1e6 vq = (3 * 3.5e6) / (2 * spin * (2 * spin - 1)) v_iso = -9 * 17 / 8 + 1e6 / 8 * ((vq / v0)**2) * ((0.36**2) / 3 + 1) # the coordinate from spectrum along the iso dimension must be equal to v_iso v_iso_spectrum = data.x[1].coordinates[index[0]].value np.testing.assert_almost_equal(v_iso, v_iso_spectrum, decimal=2) # The projection onto the MAS dimension should be the 1D block decay central # transition spectrum mas_slice = data.sum(axis=1).y[0].components[0] # MAS spectrum method = BlochDecayCTSpectrum( channels=["87Rb"], magnetic_flux_density=9.4, rotor_frequency=1e9, spectral_dimensions=[{ "count": 512, "spectral_width": 20000 }], ) sim = Simulator() sim.spin_systems = [spin_system] sim.methods = [method] sim.config.integration_volume = "hemisphere" sim.run() data = sim.methods[0].simulation.y[0].components[0] assert np.allclose(data / data.max(), mas_slice / mas_slice.max())
# investigation. For the current example, we know that Cuspidine is a crystalline silica # polymorph with one crystallographic Si site. Therefore, our initial guess model is a # single :math:`^{29}\text{Si}` site spin system. For non-linear fitting algorithms, as # a general recommendation, the initial guess model parameters should be a good starting # point for the algorithms to converge. # the guess model comprising of a single site spin system site = Site( isotope="29Si", isotropic_chemical_shift=-82.0, # in ppm, shielding_symmetric=SymmetricTensor(zeta=-63, eta=0.4), # zeta in ppm ) spin_system = SpinSystem( name="Si Site", description="A 29Si site in cuspidine", sites=[site], # from the above code abundance=100, ) # %% # **Step 2:** Create the method object. # # The method should be the same as the one used # in the measurement. In this example, we use the `BlochDecaySpectrum` method. Note, # when creating the method object, the value of the method parameters must match the # respective values used in the experiment. MAS = BlochDecaySpectrum( channels=["29Si"], magnetic_flux_density=7.1, # in T rotor_frequency=780, # in Hz spectral_dimensions=[
def test_MQMAS_spin_5halves(): spin_system = SpinSystem(sites=[ Site( isotope="27Al", isotropic_chemical_shift=64.5, # in ppm quadrupolar={ "Cq": 3.22e6, "eta": 0.66 }, # Cq is in Hz ) ]) method = ThreeQ_VAS( channels=["27Al"], magnetic_flux_density=7, spectral_dimensions=[ { "count": 1024, "spectral_width": 5000, "reference_offset": -3e3 }, { "count": 512, "spectral_width": 10000, "reference_offset": 4e3 }, ], ) sim = Simulator() sim.spin_systems = [spin_system] sim.methods = [method] sim.run() data = sim.methods[0].simulation dat = data.y[0].components[0] index = np.where(dat == dat.max())[0] # The isotropic coordinate of this peak is given by # v_iso = -(17/31)*iso_shift + 8e6/93 * (vq/v0)^2 * (eta^2 / 3 + 1) # ref: D. Massiot et al. / Solid State Nuclear Magnetic Resonance 6 (1996) 73-83 spin = method.channels[0].spin v0 = method.channels[0].gyromagnetic_ratio * 7 * 1e6 vq = 3 * 3.22e6 / (2 * spin * (2 * spin - 1)) v_iso = -(17 / 31) * 64.5 - (8e6 / 93) * (vq / v0)**2 * ((0.66**2) / 3 + 1) # the coordinate from spectrum along the iso dimension must be equal to v_iso v_iso_spectrum = data.x[1].coordinates[index[0]].value np.testing.assert_almost_equal(v_iso, v_iso_spectrum, decimal=2) # The projection onto the MAS dimension should be the 1D block decay central # transition spectrum mas_slice = data.sum(axis=1).y[0].components[0] # MAS spectrum method = BlochDecayCTSpectrum( channels=["27Al"], magnetic_flux_density=7, rotor_frequency=1e9, spectral_dimensions=[{ "count": 512, "spectral_width": 10000, "reference_offset": 4e3 }], ) sim = Simulator() sim.spin_systems = [spin_system] sim.methods = [method] sim.config.integration_volume = "hemisphere" sim.run() data = sim.methods[0].simulation.y[0].components[0] assert np.allclose(data / data.max(), mas_slice / mas_slice.max())
# %% # Generate the site and spin system objects. site = Site( isotope="87Rb", isotropic_chemical_shift=-9, # in ppm shielding_symmetric=SymmetricTensor(zeta=110, eta=0), quadrupolar=SymmetricTensor( Cq=3.5e6, # in Hz eta=0.36, alpha=0, # in rads beta=70 * 3.14159 / 180, # in rads gamma=0, # in rads ), ) spin_system = SpinSystem(sites=[site]) # %% # Use the generic 2D method, `Method2D`, to simulate a COASTER spectrum by customizing # the method parameters, as shown below. Note, the Method2D method simulates an infinite # spinning speed spectrum. coaster = Method2D( name="COASTER", channels=["87Rb"], magnetic_flux_density=9.4, # in T rotor_angle=70.12 * 3.14159 / 180, # in rads spectral_dimensions=[ SpectralDimension( count=256, spectral_width=4e4, # in Hz reference_offset=-8e3, # in Hz
"Cq": 4.2e6, "eta": 0.5 }, # Cq in Hz ) O2 = Site( isotope="17O", isotropic_chemical_shift=40.0, # in ppm, quadrupolar={ "Cq": 2.4e6, "eta": 0 }, # Cq in Hz ) spin_systems = [ SpinSystem(sites=[O1], abundance=50, name="O1"), SpinSystem(sites=[O2], abundance=50, name="O2"), ] # %% # **Step 2:** Create the method object. Create an appropriate method object that closely # resembles the technique used in acquiring the experimental data. The attribute values # of this method must meet the experimental conditions, including the acquisition # channels, the magnetic flux density, rotor angle, rotor frequency, and the # spectral/spectroscopic dimension. # # In the following example, we set up a central transition selective Bloch decay # spectrum method where the spectral/spectroscopic dimension information, i.e., count, # spectral_width, and the reference_offset, is extracted from the CSDM dimension # metadata using the :func:`~mrsimulator.utils.get_spectral_dimensions` utility # function. The remaining attribute values are set to the experimental conditions.
def test_direct_init_spin_system(): # test-1 # empty spin system the_spin_system = SpinSystem(sites=[], abundance=10, transition_pathways=None) assert the_spin_system.sites == [] assert the_spin_system.abundance == 10.0 assert the_spin_system.transition_pathways is None assert the_spin_system.json() == {"abundance": "10.0 %"} assert the_spin_system.json(units=False) == { "abundance": 10.0, } # test-2 # site test_site = Site(isotope="29Si", isotropic_chemical_shift=10) assert test_site.isotope.symbol == "29Si" assert test_site.isotropic_chemical_shift == 10.0 assert test_site.property_units["isotropic_chemical_shift"] == "ppm" assert test_site.json() == { "isotope": "29Si", "isotropic_chemical_shift": "10.0 ppm", } assert test_site.json(units=False) == { "isotope": "29Si", "isotropic_chemical_shift": 10.0, } # test-3 # one site spin system the_spin_system = SpinSystem(sites=[test_site], abundance=10) assert isinstance(the_spin_system.sites[0], Site) assert the_spin_system.abundance == 10.0 assert the_spin_system.json() == { "sites": [{ "isotope": "29Si", "isotropic_chemical_shift": "10.0 ppm" }], "abundance": "10.0 %", } assert the_spin_system.json(units=False) == { "sites": [{ "isotope": "29Si", "isotropic_chemical_shift": 10.0 }], "abundance": 10, } # test-4 # two sites spin system the_spin_system = SpinSystem(sites=[test_site, test_site], abundance=10) assert isinstance(the_spin_system.sites[0], Site) assert isinstance(the_spin_system.sites[1], Site) assert id(the_spin_system.sites[0]) != id(the_spin_system.sites[1]) assert the_spin_system.abundance == 10.0 assert the_spin_system.json() == { "sites": [ { "isotope": "29Si", "isotropic_chemical_shift": "10.0 ppm" }, { "isotope": "29Si", "isotropic_chemical_shift": "10.0 ppm" }, ], "abundance": "10.0 %", } assert the_spin_system.json(units=False) == { "sites": [ { "isotope": "29Si", "isotropic_chemical_shift": 10.0 }, { "isotope": "29Si", "isotropic_chemical_shift": 10.0 }, ], "abundance": 10, } # test-5 # coupling test_coupling = Coupling(site_index=[0, 1], isotropic_j=10, dipolar={"D": 100}) assert test_coupling.site_index == [0, 1] assert test_coupling.isotropic_j == 10.0 assert test_coupling.property_units["isotropic_j"] == "Hz" assert test_coupling.dipolar.D == 100.0 assert test_coupling.dipolar.property_units["D"] == "Hz" assert test_coupling.json() == { "site_index": [0, 1], "isotropic_j": "10.0 Hz", "dipolar": { "D": "100.0 Hz" }, } assert test_coupling.json(units=False) == { "site_index": [0, 1], "isotropic_j": 10.0, "dipolar": { "D": 100.0 }, } # test-6 # two sites and one coupling spin system the_spin_system = SpinSystem(sites=[test_site, test_site], couplings=[test_coupling], abundance=10) assert isinstance(the_spin_system.sites[0], Site) assert isinstance(the_spin_system.sites[1], Site) assert isinstance(the_spin_system.couplings[0], Coupling) assert id(the_spin_system.sites[0]) != id(the_spin_system.sites[1]) assert the_spin_system.abundance == 10.0 assert the_spin_system.json() == { "sites": [ { "isotope": "29Si", "isotropic_chemical_shift": "10.0 ppm" }, { "isotope": "29Si", "isotropic_chemical_shift": "10.0 ppm" }, ], "couplings": [{ "site_index": [0, 1], "isotropic_j": "10.0 Hz", "dipolar": { "D": "100.0 Hz" }, }], "abundance": "10.0 %", } assert the_spin_system.json(units=False) == { "sites": [ { "isotope": "29Si", "isotropic_chemical_shift": 10.0 }, { "isotope": "29Si", "isotropic_chemical_shift": 10.0 }, ], "couplings": [{ "site_index": [0, 1], "isotropic_j": 10.0, "dipolar": { "D": 100.0 } }], "abundance": 10, } # test-5 the_spin_system = SpinSystem( name="Just a test", description="The same", sites=[ { "isotope": "1H", "isotropic_chemical_shift": 0 }, { "isotope": "17O", "isotropic_chemical_shift": -10, "quadrupolar": { "Cq": 5.1e6, "eta": 0.5 }, }, ], couplings=[{ "site_index": [0, 1], "isotropic_j": 34 }], abundance=4.23, ) assert the_spin_system.name == "Just a test" assert the_spin_system.description == "The same" assert the_spin_system.sites[0].isotope.symbol == "1H" assert the_spin_system.sites[0].isotropic_chemical_shift == 0 assert the_spin_system.sites[1].isotope.symbol == "17O" assert the_spin_system.sites[1].isotropic_chemical_shift == -10 assert the_spin_system.sites[1].quadrupolar.Cq == 5.1e6 assert the_spin_system.sites[1].quadrupolar.eta == 0.5 assert the_spin_system.couplings[0].site_index == [0, 1] assert the_spin_system.couplings[0].isotropic_j == 34.0 assert the_spin_system.abundance == 4.23 serialize = the_spin_system.json() assert serialize == { "name": "Just a test", "description": "The same", "sites": [ { "isotope": "1H", "isotropic_chemical_shift": "0.0 ppm" }, { "isotope": "17O", "isotropic_chemical_shift": "-10.0 ppm", "quadrupolar": { "Cq": "5100000.0 Hz", "eta": 0.5 }, }, ], "couplings": [{ "site_index": [0, 1], "isotropic_j": "34.0 Hz" }], "abundance": "4.23 %", } assert the_spin_system == SpinSystem.parse_dict_with_units(serialize) json_no_unit = the_spin_system.json(units=False) assert json_no_unit == { "name": "Just a test", "description": "The same", "sites": [ { "isotope": "1H", "isotropic_chemical_shift": 0 }, { "isotope": "17O", "isotropic_chemical_shift": -10.0, "quadrupolar": { "Cq": 5100000, "eta": 0.5 }, }, ], "couplings": [{ "site_index": [0, 1], "isotropic_j": 34.0 }], "abundance": 4.23, } assert the_spin_system == SpinSystem(**json_no_unit)
def test_warnings(): s = SpinSystem(sites=[Site(isotope="23Na")]) m = Method1D(channels=["1H"]) assert m.get_transition_pathways(s) == []
def get_spin_system_list(): isotopes = [ "19F", "31P", "2H", "6Li", "14N", "27Al", "25Mg", "45Sc", "87Sr" ] return SpinSystem(sites=[Site(isotope=item) for item in isotopes])