Files
frappy/frappy_psi/tnmr/sequence_generation.py

131 lines
5.3 KiB
Python

# -*- coding: utf-8 -*-
"""
sequence_generation
______________________
Version: 1.0
Authors: Davis Garrad (Paul Scherrer Institute, CH)
______________________
Wrapper for the API I wrote to generate pulse sequences programmatically in TNMR (Tecmag), with a little more polish than the original driver code.
"""
import frappy_psi.tnmr.sequence_fileformat as se
from pydantic.utils import deep_update
import json
def get_single_pulse_block(name, pulse_width, pulse_height, delay_time, phase_cycle='0'):
'''Generates a single block of data to create a sequence with.
Parameters
----------
name: str, just the prefix for the column names. Ensure this is unique over the whole sequence!
pulse_width: str, in the format '10u' (10 microseconds)
pulse_height: str, in the format '40' (I'm not honestly sure what units this is in...)
delay_time: str, in the format '10u'. Minimum value of '0.1u'. If '0', then only the pulse is added (not the delay afterwards). If no unit is given, assumes microseconds.
phase_cycle: a given phase cycle (steps of 4, 0-3 inc.) (eg., '0 0 1 1 2 3 0 1', etc.) (default '0')
Returns
-------
a dictionary which can be updated with others (with combine_blocks) to generate a larger, more complex sequence.
'''
if(delay_time.strip()[-1] == 'u'):
delay_time = float(delay_time.strip()[:-1])
elif(delay_time.strip()[-1] == 'n'):
delay_time = float(delay_time.strip()[:-1]) * 1000
elif(delay_time.strip()[-1] == 'm'):
delay_time = float(delay_time.strip()[:-1]) / 1e3
elif(delay_time.strip()[-1] == 's'):
delay_time = float(delay_time.strip()[:-1]) / 1e6
else:
delay_time = float(delay_time.strip()) # assume in us
delay_time_rounded = int(delay_time*10) / 10 # nearest 10ns
delay_time = delay_time_rounded
ph = name + '_phase'
rl = name + '_delay'
block = se.generate_default_sequence([ ph, rl ] if delay_time > 0 else [ ph ], [ pulse_width, str(delay_time) + 'u' ] if delay_time > 0 else [pulse_width])
# COLUMNNS
# PH column
block['columns'][ph]['F1_Ampl']['value'] = str(pulse_height)
block['columns'][ph]['Delay'] = str(pulse_width)
block['columns'][ph]['F1_UnBlank']['value'] = '1'
block['columns'][ph]['Rx_Blank']['value'] = '1'
if(delay_time > 0):
# delay column
block['columns'][rl]['F1_UnBlank']['value'] = '1'
block['columns'][rl]['Rx_Blank']['value'] = '1'
if(phase_cycle != ''):
table_name = f'ph_{name}'
block['columns'][ph]['F1_Ph']['table'] = table_name
block['tables'][table_name] = { 'values': phase_cycle, 'typestr': 'HP', 'start': 1 }
return block
def get_initial_block():
'''Generates the mandatory initial block, which consists of phase reset and unblanking
Returns
-------
a dictionary which can be updated with others (with combine_blocks) to generate a larger, more complex sequence.
'''
block = se.generate_default_sequence(['Phase reset', 'Unblank'], ['1u', '10u'])
# Phase reset
block['columns']['Phase reset']['F1_PhRst']['value'] = '1'
# Unblank
block['columns']['Unblank']['F1_UnBlank']['value'] = '1'
block['columns']['Unblank']['Rx_Blank']['value'] = '1'
return block
def get_final_block(ringdown_time, preacquire_time, acquire_time, cooldown_time, acq_phase_cycle='0', num_acq_points=1024):
'''Generates the final block of data to create a sequence with.
Parameters
----------
ringdown_time: str, of format '10u', how long to ringdown
preacquire_time: str, in the format '10u', how long to wait after ringdown before acquiring data. A good default is 1 usec
acquire_time: str, in the format '10u', how long to acquire data for
cooldown_time: str, in the format '40u', how long to wait after acquisition.
acq_phase_cycle: str, the phase cycle that the acquisition should follow
Returns
-------
a dictionary which can be updated with others to generate a larger, more complex sequence.
'''
block = se.generate_default_sequence(['Ringdown', 'RX On', 'Acquisition', 'Finish', ''], [ringdown_time, preacquire_time, acquire_time, cooldown_time, '1u'])
block['num_acq_points'] = num_acq_points
# ringdown
block['columns']['Ringdown']['Rx_Blank']['value'] = '1'
# Acquire
block['columns']['Acquisition']['Acq']['value'] = '1'
#block['columns']['Acquisition']['Acq_phase']['value'] = '0'
if(acq_phase_cycle != ''):
block['columns']['Acquisition']['Acq_phase']['table'] = 'phacq'
block['tables']['phacq'] = { 'values': acq_phase_cycle, 'typestr': 'HP', 'start': 1 }
return block
def combine_blocks(l, r):
'''Combines two dictionaries (just deep copies them, as they each have recursive dictionaries'''
return deep_update(l, r)
def save_sequence(filename, sequence):
'''Saves the given sequence to a file'''
se.create_sequence_file(filename, sequence)
def save_sequence_cfg(filename, sequence):
'''Saves the sequence to a file in JSON form to be read easier. Appends the extension ".cfg" '''
with open(filename + '.cfg', 'w') as file:
json.dump(sequence, file, indent=4)
file.close()