Usage ===== Running a Pulse Sequence ------------------------ Pulse sequence programs are used programmatically via the :class:`~matipo.sequence.Sequence` class. Below is the code for a typical usage scenario. This code may be run directly in a jupyter notebook. Load a pulse sequence by passing its file path:: from matipo import SEQUENCE_DIR, GLOBALS_DIR from matipo.sequence import Sequence # load system FID pulse sequence fid_seq = Sequence(SEQUENCE_DIR+'FID.py') Load parameter files by passing their file paths to the :meth:`~matipo.sequence.Sequence.loadpar` method:: # load system frequency and pulse calibration files fid_seq.loadpar(GLOBALS_DIR+'frequency.yaml') fid_seq.loadpar(GLOBALS_DIR+'hardpulse_90.yaml') Set parameters directly using the :meth:`~matipo.sequence.Sequence.setpar` method:: fid_seq.setpar( n_scans=1, n_samples=10000, t_dw=10e-6, t_end=1 ) The current set of parameters can be read using the :attr:`~matipo.sequence.Sequence.par` property:: # print all parameters print(fid_seq.par) # use parameters to calculate total acquisition time: (dwell time)*(samples) print('Acquisition time:', fid_seq.par.t_dw*fid_seq.par.n_samples) .. note:: All parameters have a default value set in the pulse sequence program which will be used if not overriden by :meth:`~matipo.sequence.Sequence.loadpar` or :meth:`~matipo.sequence.Sequence.setpar`. Run the pulse sequence program using the :meth:`~matipo.sequence.Sequence.run` method, which also returns the data. This is an ``async`` method, which means that ``await`` must be used to schedule it to run and obtain the result when finished:: data = await fid_seq.run() The data from a sequence object's last run can also be accessed later with the :attr:`~matipo.sequence.Sequence.data` property:: data = fid_seq.data Data is returned as a numpy array of complex values. This data may be plotted with matplotlib:: import matplotlib.pyplot as plt plt.plot(data.real, label='real') plt.plot(data.imag, label='imag') plt.legend() plt.show() Parameters may be saved to a file using the :meth:`~matipo.sequence.Sequence.savepar` method:: fid_seq.savepar('FID_example.yaml') This file can then be loaded with :meth:`~matipo.sequence.Sequence.loadpar` to restore the same parameters at a later date. Data may be saved using numpy:: import numpy as np np.save('FID_example.npy', data) Writing Custom Pulse Sequence Programs -------------------------------------- Template ^^^^^^^^ There is some boilerplate that all sequence programs must have. Here is a simple sequence which runs a single acquisition on a channel, and can be used as a template:: from matipo.sequence import ParDef from collections import namedtuple import numpy as np # Define input parameters (must be named 'PARDEF') PARDEF = [ # args: name, dtype, default value ParDef('f', float, 1e6, unit='Hz'), ParDef('t_dw', float, 1e-6, min=0.1e-6, max=160e-6, unit='s'), ParDef('n_samples', int, 1000, min=2, unit=''), ParDef('rx_chan', int, 0, unit='') ] # Create parameter set type (must be named 'ParameterSet') ParameterSet = namedtuple('ParameterSet', [pd.name for pd in PARDEF]) # Main pulse sequence function, taking the sequencer object and a parameter set def main(seq, par: ParameterSet): t_acq = par.n_samples * par.t_dw t_flatfilter_groupdelay = 10*par.t_dw yield seq.rx[par.rx_chan].freq(par.f) yield seq.rx[par.rx_chan].dwelltime(par.t_dw) yield seq.rx[par.rx_chan].mode(flatfilter=True) # Wait filter group delay after changing acquisition settings to avoid edge effects yield seq.wait(t_flatfilter_groupdelay) # Start acquisition with ID=0 and the desired number of samples yield seq.rx[par.rx_chan].acquire(0, par.n_samples) # Allow acquisition to finish completely before ending the sequence yield seq.wait(t_acq + t_flatfilter_groupdelay) Input Parameter Definition ^^^^^^^^^^^^^^^^^^^^^^^^^^ The input parameters are defined in the :code:`PARDEF` list:: PARDEF = [ # args: name, dtype, default value ParDef('f', float, 1e6, unit='Hz'), ParDef('t_dw', float, 1e-6, min=0.1e-6, max=160e-6, unit='s'), ParDef('n_samples', int, 1000, min=2, unit=''), ParDef('rx_chan', int, 0, unit='') ] Where each parameter is defined with :code:`ParDef(, , )`. ``min``, ``max``, and ``unit`` may also be defined, and will be read by experiments using :class:`~matipo.experiment.BaseExperiment` to set limits on the inputs. The `Pint library `_ is used to convert units if Pint Quantities are passed into the :meth:`~matipo.sequence.Sequence.setpar` method. The :obj:`matipo.sequence.floatarray` data type may be used to pass numpy arrays as parameters. Main Function ^^^^^^^^^^^^^ The :code:`main()` function is the actual pulse sequence program:: def main(seq, par: ParameterSet): t_acq = par.n_samples * par.t_dw t_flatfilter_groupdelay = 10*par.t_dw yield seq.rx[par.rx_chan].freq(par.f) yield seq.rx[par.rx_chan].dwelltime(par.t_dw) yield seq.rx[par.rx_chan].mode(flatfilter=True) # Wait filter group delay after changing acquisition settings to avoid edge effects yield seq.wait(t_flatfilter_groupdelay) # Start acquisition with ID=0 and the desired number of samples yield seq.rx[par.rx_chan].acquire(0, par.n_samples) # Allow acquisition to finish completely before ending the sequence yield seq.wait(t_acq + t_flatfilter_groupdelay) This function is a python generator which returns pulse sequence commands using Python's ``yield`` syntax. Pulse Sequence Commands ^^^^^^^^^^^^^^^^^^^^^^^ Pulse sequence commands are methods of the Sequencer object and its children: ``seq`` (:class:`~matipo.hardware.sequence_inst.SequencerTop`): * ``wait(