Working simulation.

This commit is contained in:
jupfi 2024-05-29 07:38:03 +02:00
parent 70266387e2
commit f4ac146611
8 changed files with 771 additions and 20 deletions

View file

@ -0,0 +1,17 @@
from quackseq.spectrometer.spectrometer import Spectrometer
from .simulator_model import SimulatorModel
from .simulator_controller import SimulatorController
class Simulator(Spectrometer):
def __init__(self):
self.model = SimulatorModel()
self.controller = SimulatorController(self.model)
def run_sequence(self, sequence):
result =self.controller.run_sequence(sequence)
return result
def set_averages(self, value: int):
self.model.average = value

View file

@ -0,0 +1,354 @@
"""The controller module for the simulator spectrometer."""
import logging
from datetime import datetime
import numpy as np
from quackseq.spectrometer.spectrometer_controller import SpectrometerController
from quackseq.measurement import Measurement
from quackseq.pulseparameters import TXPulse, RXReadout
from quackseq.pulsesequence import QuackSequence
from nqr_blochsimulator.classes.pulse import PulseArray
from nqr_blochsimulator.classes.sample import Sample
from nqr_blochsimulator.classes.simulation import Simulation
logger = logging.getLogger(__name__)
class SimulatorController(SpectrometerController):
"""The controller class for the nqrduck simulator module."""
def __init__(self, model):
"""Initializes the SimulatorController."""
super().__init__()
self.model = model
def run_sequence(self, sequence: QuackSequence) -> None:
"""This method is called when the start_measurement signal is received from the core.
It will becalled if the simulator is the active spectrometer.
This will start the simulation based on the settings and the pulse sequence.
"""
logger.debug("Starting simulation")
sample = self.get_sample_from_settings()
logger.debug("Sample: %s", sample.name)
dwell_time = self.calculate_dwelltime(sequence)
logger.debug("Dwell time: %s", dwell_time)
try:
pulse_array = self.translate_pulse_sequence(sequence, dwell_time)
except AttributeError:
logger.warning("Could not translate pulse sequence")
return
simulation = self.get_simulation(sample, pulse_array)
result = simulation.simulate()
tdx = (
np.linspace(0, float(self.calculate_simulation_length(sequence)), len(result)) * 1e6
)
rx_begin, rx_stop = self.translate_rx_event(sequence)
# If we have a RX event, we need to cut the result to the RX event
if rx_begin and rx_stop:
evidx = np.where((tdx > rx_begin) & (tdx < rx_stop))[0]
tdx = tdx[evidx]
result = result[evidx]
# Measurement name date + module + target frequency + averages + sequence name
name = f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Simulator - {self.model.target_frequency / 1e6} MHz - {self.model.averages} averages - {sequence.name}"
logger.debug(f"Measurement name: {name}")
measurement_data = Measurement(
name,
tdx,
result / simulation.averages,
sample.resonant_frequency,
# frequency_shift=self.module.model.if_frequency,
)
return measurement_data
def get_sample_from_settings(self) -> Sample:
"""This method creates a sample object based on the settings in the model.
Returns:
Sample: The sample object created from the settings.
"""
model = self.model
atom_density = None
sample_volume = None
sample_length = None
sample_diameter = None
for samplesetting in model.settings[self.model.SAMPLE]:
logger.debug("Sample setting: %s", samplesetting.name)
if samplesetting.name == model.NAME:
name = samplesetting.value
elif samplesetting.name == model.DENSITY:
density = float(samplesetting.value)
elif samplesetting.name == model.MOLAR_MASS:
molar_mass = float(samplesetting.value)
elif samplesetting.name == model.RESONANT_FREQUENCY:
resonant_frequency = float(samplesetting.value)
elif samplesetting.name == model.GAMMA:
gamma = float(samplesetting.value)
elif samplesetting.name == model.NUCLEAR_SPIN:
nuclear_spin = float(samplesetting.value)
elif samplesetting.name == model.SPIN_FACTOR:
spin_factor = float(samplesetting.value)
elif samplesetting.name == model.POWDER_FACTOR:
powder_factor = float(samplesetting.value)
elif samplesetting.name == model.FILLING_FACTOR:
filling_factor = float(samplesetting.value)
elif samplesetting.name == model.T1:
T1 = float(samplesetting.value)
elif samplesetting.name == model.T2:
T2 = float(samplesetting.value)
elif samplesetting.name == model.T2_STAR:
T2_star = float(samplesetting.value)
elif samplesetting.name == model.ATOM_DENSITY:
atom_density = float(samplesetting.value)
elif samplesetting.name == model.SAMPLE_VOLUME:
sample_volume = float(samplesetting.value)
elif samplesetting.name == model.SAMPLE_LENGTH:
sample_length = float(samplesetting.value)
elif samplesetting.name == model.SAMPLE_DIAMETER:
sample_diameter = float(samplesetting.value)
else:
logger.warning("Unknown sample setting: %s", samplesetting.name)
self.module.nqrduck_signal.emit(
"notification",
["Error", "Unknown sample setting: " + samplesetting.name],
)
return None
sample = Sample(
name=name,
density=density,
molar_mass=molar_mass,
resonant_frequency=resonant_frequency,
gamma=gamma,
nuclear_spin=nuclear_spin,
spin_factor=spin_factor,
powder_factor=powder_factor,
filling_factor=filling_factor,
T1=T1,
T2=T2,
T2_star=T2_star,
atom_density=atom_density,
sample_volume=sample_volume,
sample_length=sample_length,
sample_diameter=sample_diameter,
)
return sample
def translate_pulse_sequence(self, sequence : QuackSequence, dwell_time: float) -> PulseArray:
"""This method translates the pulse sequence from the core to a PulseArray object needed for the simulation.
Args:
sequence (QuackSequence): The pulse sequence from the core.
dwell_time (float): The dwell time in seconds.
Returns:
PulseArray: The pulse sequence translated to a PulseArray object.
"""
events = sequence.events
amplitude_array = list()
for event in events:
logger.debug("Event %s has parameters: %s", event.name, event.parameters)
for parameter in event.parameters.values():
logger.debug(
"Parameter %s has options: %s", parameter.name, parameter.options
)
if (
parameter.name == sequence.TX_PULSE
and parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value
> 0
):
logger.debug(f"Adding pulse: {event.duration} s")
# If we have a pulse, we need to add it to the pulse array
pulse_shape = parameter.get_option_by_name(
TXPulse.TX_PULSE_SHAPE
).value
pulse_amplitude = abs(
pulse_shape.get_pulse_amplitude(
event.duration, resolution=dwell_time
)
)
amplitude_array.append(pulse_amplitude)
elif (
parameter.name == sequence.TX_PULSE
and parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value
== 0
):
# If we have a wait, we need to add it to the pulse array
amplitude_array.append(np.zeros(int(event.duration / dwell_time)))
amplitude_array = np.concatenate(amplitude_array)
# This has not yet been implemented right now the phase is always 0
phase_array = np.zeros(len(amplitude_array))
pulse_array = PulseArray(
pulseamplitude=amplitude_array,
pulsephase=phase_array,
dwell_time=float(dwell_time),
)
return pulse_array
def get_simulation(self, sample: Sample, pulse_array: PulseArray) -> Simulation:
"""This method creates a simulation object based on the settings and the pulse sequence.
Args:
sample (Sample): The sample object created from the settings.
pulse_array (PulseArray): The pulse sequence translated to a PulseArray object.
Returns:
Simulation: The simulation object created from the settings and the pulse sequence.
"""
model = self.model
# noise = float(model.get_setting_by_name(model.NOISE).value)
simulation = Simulation(
sample=sample,
pulse=pulse_array,
number_isochromats=int(
model.get_setting_by_name(model.NUMBER_ISOCHROMATS).value
),
initial_magnetization=float(
model.get_setting_by_name(model.INITIAL_MAGNETIZATION).value
),
gradient=float(model.get_setting_by_name(model.GRADIENT).value),
noise=float(model.get_setting_by_name(model.NOISE).value),
length_coil=float(model.get_setting_by_name(model.LENGTH_COIL).value),
diameter_coil=float(model.get_setting_by_name(model.DIAMETER_COIL).value),
number_turns=float(model.get_setting_by_name(model.NUMBER_TURNS).value),
q_factor_transmit=float(
model.get_setting_by_name(model.Q_FACTOR_TRANSMIT).value
),
q_factor_receive=float(
model.get_setting_by_name(model.Q_FACTOR_RECEIVE).value
),
power_amplifier_power=float(
model.get_setting_by_name(model.POWER_AMPLIFIER_POWER).value
),
gain=float(model.get_setting_by_name(model.GAIN).value),
temperature=float(model.get_setting_by_name(model.TEMPERATURE).value),
averages=int(model.averages),
loss_TX=float(model.get_setting_by_name(model.LOSS_TX).value),
loss_RX=float(model.get_setting_by_name(model.LOSS_RX).value),
conversion_factor=float(
model.get_setting_by_name(model.CONVERSION_FACTOR).value
),
)
return simulation
def calculate_dwelltime(self, sequence : QuackSequence) -> float:
"""This method calculates the dwell time based on the settings and the pulse sequence.
Returns:
float: The dwell time in seconds.
"""
n_points = int(
self.model.get_setting_by_name(self.model.NUMBER_POINTS).value
)
simulation_length = self.calculate_simulation_length(sequence)
dwell_time = simulation_length / n_points
return dwell_time
def calculate_simulation_length(self, sequence : QuackSequence) -> float:
"""This method calculates the simulation length based on the settings and the pulse sequence.
Returns:
float: The simulation length in seconds.
"""
events = sequence.events
simulation_length = 0
for event in events:
simulation_length += event.duration
return simulation_length
def translate_rx_event(self, sequence : QuackSequence) -> tuple:
"""This method translates the RX event of the pulse sequence to the limr object.
Returns:
tuple: A tuple containing the start and stop time of the RX event in µs
"""
# This is a correction factor for the RX event. The offset of the first pulse is 2.2µs longer than from the specified samples.
events = sequence.events
previous_events_duration = 0
# offset = 0
rx_duration = 0
for event in events:
logger.debug("Event %s has parameters: %s", event.name, event.parameters)
for parameter in event.parameters.values():
logger.debug(
"Parameter %s has options: %s", parameter.name, parameter.options
)
if (
parameter.name == sequence.RX_READOUT
and parameter.get_option_by_name(RXReadout.RX).value
):
# Get the length of all previous events
previous_events = events[: events.index(event)]
previous_events_duration = sum(
[event.duration for event in previous_events]
)
rx_duration = event.duration
rx_begin = float(previous_events_duration)
if rx_duration:
rx_stop = rx_begin + float(rx_duration)
return rx_begin * 1e6, rx_stop * 1e6
else:
return None, None
def set_frequency(self, value: str) -> None:
"""This method is called when the set_frequency signal is received from the core.
For the simulator this just prints a warning that the simulator is selected.
Args:
value (str) : The new frequency in MHz.
"""
logger.debug("Setting frequency to: %s", value)
try:
self.module.model.target_frequency = float(value)
logger.debug("Successfully set frequency to: %s", value)
except ValueError:
logger.warning("Could not set frequency to: %s", value)
self.module.nqrduck_signal.emit(
"notification", ["Error", "Could not set frequency to: " + value]
)
self.module.nqrduck_signal.emit("failure_set_frequency", value)
def set_averages(self, value: str) -> None:
"""This method is called when the set_averages signal is received from the core.
It sets the averages in the model used for the simulation.
Args:
value (str): The value to set the averages to.
"""
logger.debug("Setting averages to: %s", value)
try:
self.module.model.averages = int(value)
logger.debug("Successfully set averages to: %s", value)
except ValueError:
logger.warning("Could not set averages to: %s", value)
self.module.nqrduck_signal.emit(
"notification", ["Error", "Could not set averages to: " + value]
)
self.module.nqrduck_signal.emit("failure_set_averages", value)

View file

@ -0,0 +1,327 @@
"""The model module for the simulator spectrometer."""
import logging
from quackseq.spectrometer.spectrometer_model import SpectrometerModel
from quackseq.spectrometer.spectrometer_settings import IntSetting, FloatSetting, StringSetting
from quackseq.pulseparameters import TXPulse, RXReadout
logger = logging.getLogger(__name__)
class SimulatorModel(SpectrometerModel):
"""Model class for the simulator spectrometer."""
# Simulation settings
NUMBER_POINTS = "N. simulation points"
NUMBER_ISOCHROMATS = "N. of isochromats"
INITIAL_MAGNETIZATION = "Initial magnetization"
GRADIENT = "Gradient (mT/m))"
NOISE = "Noise (uV)"
# Hardware settings
LENGTH_COIL = "Length coil (m)"
DIAMETER_COIL = "Diameter coil (m)"
NUMBER_TURNS = "Number turns"
Q_FACTOR_TRANSMIT = "Q factor Transmit"
Q_FACTOR_RECEIVE = "Q factor Receive"
POWER_AMPLIFIER_POWER = "PA power (W)"
GAIN = "Gain"
TEMPERATURE = "Temperature (K)"
AVERAGES = "Averages"
LOSS_TX = "Loss TX (dB)"
LOSS_RX = "Loss RX (dB)"
CONVERSION_FACTOR = "Conversion factor"
# Sample settings, this will be done in a separate module later on
NAME = "Name"
DENSITY = "Density (g/cm^3)"
MOLAR_MASS = "Molar mass (g/mol)"
RESONANT_FREQUENCY = "Resonant freq. (Hz)"
GAMMA = "Gamma (Hz/T)"
NUCLEAR_SPIN = "Nuclear spin"
SPIN_FACTOR = "Spin factor"
POWDER_FACTOR = "Powder factor"
FILLING_FACTOR = "Filling factor"
T1 = "T1 (s)"
T2 = "T2 (s)"
T2_STAR = "T2* (s)"
ATOM_DENSITY = "Atom density (1/cm^3)"
SAMPLE_VOLUME = "Sample volume (m^3)"
SAMPLE_LENGTH = "Sample length (m)"
SAMPLE_DIAMETER = "Sample diameter (m)"
# Categories of the settings
SIMULATION = "Simulation"
HARDWARE = "Hardware"
EXPERIMENTAL_Setup = "Experimental Setup"
SAMPLE = "Sample"
def __init__(self):
"""Initializes the SimulatorModel."""
super().__init__()
# Simulation settings
number_of_points_setting = IntSetting(
self.NUMBER_POINTS,
8192,
"Number of points used for the simulation. This influences the dwell time in combination with the total event simulation given by the pulse sequence.",
min_value=0,
)
self.add_setting(
number_of_points_setting,
self.SIMULATION,
)
number_of_isochromats_setting = IntSetting(
self.NUMBER_ISOCHROMATS,
1000,
"Number of isochromats used for the simulation. This influences the computation time.",
min_value=0,
max_value=10000,
)
self.add_setting(number_of_isochromats_setting, self.SIMULATION)
initial_magnetization_setting = FloatSetting(
self.INITIAL_MAGNETIZATION,
1,
"Initial magnetization",
min_value=0,
)
self.add_setting(initial_magnetization_setting, self.SIMULATION)
# This doesn't really do anything yet
gradient_setting = FloatSetting(
self.GRADIENT,
1,
"Gradient",
)
self.add_setting(gradient_setting, self.SIMULATION)
noise_setting = FloatSetting(
self.NOISE,
2,
"Adds a specified level of random noise to the simulation to mimic real-world signal variations.",
min_value=0,
max_value=100,
)
self.add_setting(noise_setting, self.SIMULATION)
# Hardware settings
coil_length_setting = FloatSetting(
self.LENGTH_COIL,
30e-3,
"The length of the sample coil within the hardware setup.",
min_value=1e-3,
)
self.add_setting(coil_length_setting, self.HARDWARE)
coil_diameter_setting = FloatSetting(
self.DIAMETER_COIL,
8e-3,
"The diameter of the sample coil.",
min_value=1e-3,
)
self.add_setting(coil_diameter_setting, self.HARDWARE)
number_turns_setting = FloatSetting(
self.NUMBER_TURNS,
8,
"The total number of turns of the sample coil.",
min_value=1,
)
self.add_setting(number_turns_setting, self.HARDWARE)
q_factor_transmit_setting = FloatSetting(
self.Q_FACTOR_TRANSMIT,
80,
"The quality factor of the transmit path, which has an effect on the field strength for excitation.",
min_value=1,
)
self.add_setting(q_factor_transmit_setting, self.HARDWARE)
q_factor_receive_setting = FloatSetting(
self.Q_FACTOR_RECEIVE,
80,
"The quality factor of the receive path, which has an effect on the final SNR.",
min_value=1,
)
self.add_setting(q_factor_receive_setting, self.HARDWARE)
power_amplifier_power_setting = FloatSetting(
self.POWER_AMPLIFIER_POWER,
110,
"The power output capability of the power amplifier, determines the strength of pulses that can be generated.",
min_value=0.1,
)
self.add_setting(power_amplifier_power_setting, self.HARDWARE)
gain_setting = FloatSetting(
self.GAIN,
6000,
"The amplification factor of the receiver chain, impacting the final measured signal amplitude.",
min_value=0.1,
)
self.add_setting(gain_setting, self.HARDWARE)
temperature_setting = FloatSetting(
self.TEMPERATURE,
300,
"The absolute temperature during the experiment. This influences the SNR of the measurement.",
min_value=0.1,
max_value=400,
)
self.add_setting(temperature_setting, self.EXPERIMENTAL_Setup)
loss_tx_setting = FloatSetting(
self.LOSS_TX,
25,
"The signal loss occurring in the transmission path, affecting the effective RF pulse power.",
min_value=0.1,
max_value=60,
)
self.add_setting(loss_tx_setting, self.EXPERIMENTAL_Setup)
loss_rx_setting = FloatSetting(
self.LOSS_RX,
25,
"The signal loss in the reception path, which can reduce the signal that is ultimately detected.",
min_value=0.1,
max_value=60,
)
self.add_setting(loss_rx_setting, self.EXPERIMENTAL_Setup)
conversion_factor_setting = FloatSetting(
self.CONVERSION_FACTOR,
2884,
"Conversion factor (spectrometer units / V)",
)
self.add_setting(
conversion_factor_setting,
self.EXPERIMENTAL_Setup,
) # Conversion factor for the LimeSDR based spectrometer
# Sample settings
sample_name_setting = StringSetting(
self.NAME,
"BiPh3",
"The name of the sample.",
)
self.add_setting(sample_name_setting, self.SAMPLE)
density_setting = FloatSetting(
self.DENSITY,
1.585e6,
"The density of the sample. This is used to calculate the number of spins in the sample volume.",
min_value=0.1,
)
self.add_setting(density_setting, self.SAMPLE)
molar_mass_setting = FloatSetting(
self.MOLAR_MASS,
440.3,
"The molar mass of the sample. This is used to calculate the number of spins in the sample volume.",
min_value=0.1,
)
self.add_setting(molar_mass_setting, self.SAMPLE)
resonant_frequency_setting = FloatSetting(
self.RESONANT_FREQUENCY,
83.56e6,
"The resonant frequency of the observed transition.",
min_value=1e5,
)
self.add_setting(resonant_frequency_setting, self.SAMPLE)
gamma_setting = FloatSetting(
self.GAMMA,
4.342e7,
"The gyromagnetic ratio of the samples nuclei.",
min_value=0,
)
self.add_setting(gamma_setting, self.SAMPLE)
# This could be updated to a selection setting
nuclear_spin_setting = FloatSetting(
self.NUCLEAR_SPIN,
9 / 2,
"The nuclear spin of the samples nuclei.",
min_value=0,
)
self.add_setting(nuclear_spin_setting, self.SAMPLE)
spin_factor_setting = FloatSetting(
self.SPIN_FACTOR,
2,
"The spin factor represents the scaling coefficient for observable nuclear spin transitions along the x-axis, derived from the Pauli I x 0 -matrix elements.",
min_value=0,
)
self.add_setting(spin_factor_setting, self.SAMPLE)
powder_factor_setting = FloatSetting(
self.POWDER_FACTOR,
0.75,
"A factor representing the crystallinity of the solid sample. A value of 0.75 corresponds to a powder sample.",
min_value=0,
max_value=1,
)
self.add_setting(powder_factor_setting, self.SAMPLE)
filling_factor_setting = FloatSetting(
self.FILLING_FACTOR,
0.7,
"The ratio of the sample volume that occupies the coils sensitive volume.",
min_value=0,
max_value=1,
)
self.add_setting(filling_factor_setting, self.SAMPLE)
t1_setting = FloatSetting(
self.T1,
83.5e-5,
"The longitudinal or spin-lattice relaxation time of the sample, influencing signal recovery between pulses.",
min_value=1e-6,
)
self.add_setting(t1_setting, self.SAMPLE)
t2_setting = FloatSetting(
self.T2,
396e-6,
"The transverse or spin-spin relaxation time, determining the rate at which spins dephase and the signal decays in the xy plane",
min_value=1e-6,
)
self.add_setting(t2_setting, self.SAMPLE)
t2_star_setting = FloatSetting(
self.T2_STAR,
50e-6,
"The effective transverse relaxation time, incorporating effects of EFG inhomogeneities and other dephasing factors.",
min_value=1e-6,
)
self.add_setting(t2_star_setting, self.SAMPLE)
self.averages = 1
self.target_frequency = 100e6
@property
def averages(self):
"""The number of averages used for the simulation.
More averages improve the signal-to-noise ratio of the simulated signal.
"""
return self._averages
@averages.setter
def averages(self, value):
self._averages = value
@property
def target_frequency(self):
"""The target frequency for the simulation.
Doesn't do anything at the moment.
"""
return self._target_frequency
@target_frequency.setter
def target_frequency(self, value):
self._target_frequency = value

View file

@ -0,0 +1,39 @@
class Spectrometer():
"""Base class for spectrometers.
This class should be inherited by all spectrometers.
The spectrometers then need to implement the methods of this class.
"""
def run_sequence(self, sequence):
"""Starts the measurement.
This method should be called when the measurement is started.
"""
raise NotImplementedError
def set_frequency(self, value : float):
"""Sets the frequency of the spectrometer."""
raise NotImplementedError
def set_averages(self, value : int):
"""Sets the number of averages."""
raise NotImplementedError
@property
def controller(self):
"""The controller of the spectrometer."""
return self._controller
@controller.setter
def controller(self, controller):
self._controller = controller
@property
def model(self):
"""The model of the spectrometer."""
return self._model
@model.setter
def model(self, model):
self._model = model

View file

@ -0,0 +1,19 @@
"""Base class for all spectrometer controllers."""
class SpectrometerController():
"""The base class for all spectrometer controllers."""
def run_sequence(self, sequence):
"""Starts the measurement.
This method should be called when the measurement is started.
"""
raise NotImplementedError
def set_frequency(self, value : float):
"""Sets the frequency of the spectrometer."""
raise NotImplementedError
def set_averages(self, value : int):
"""Sets the number of averages."""
raise NotImplementedError

View file

@ -2,7 +2,7 @@
import logging
from collections import OrderedDict
from quackseq.spectrometer.spectrometer_setting import Setting
from quackseq.spectrometer.spectrometer_settings import Setting
logger = logging.getLogger(__name__)
@ -12,22 +12,14 @@ class SpectrometerModel():
It contains the settings and pulse parameters of the spectrometer.
Args:
module (Module) : The module that the spectrometer is connected to
Attributes:
settings (OrderedDict) : The settings of the spectrometer
"""
settings: OrderedDict
def __init__(self, module):
"""Initializes the spectrometer model.
Args:
module (Module) : The module that the spectrometer is connected to
"""
super().__init__(module)
def __init__(self):
"""Initializes the spectrometer model."""
self.settings = OrderedDict()
def add_setting(self, setting: Setting, category: str) -> None:

View file

@ -109,7 +109,6 @@ class FloatSetting(NumericalSetting):
def value(self, value):
logger.debug(f"Setting {self.name} to {value}")
self._value = float(value)
self.settings_changed.emit()
class IntSetting(NumericalSetting):
@ -130,10 +129,8 @@ class IntSetting(NumericalSetting):
description: str,
min_value=None,
max_value=None,
spin_box: tuple = (False, False),
) -> None:
"""Create a new int setting."""
self.spin_box = spin_box
super().__init__(name, description, default, min_value, max_value)
@property

View file

@ -1,8 +1,13 @@
# Dummy test to communicate the structure
import logging
import matplotlib.pyplot as plt
from quackseq.pulsesequence import QuackSequence
from quackseq.event import Event
from quackseq.functions import RectFunction
from quackseq.spectrometer.simulator import Simulator
logging.basicConfig(level=logging.DEBUG)
seq = QuackSequence("test")
@ -17,11 +22,11 @@ rect = RectFunction()
seq.set_tx_shape(tx, rect)
blank = Event("blank", "10u", seq)
blank = Event("blank", "3u", seq)
seq.add_event(blank)
rx = Event("rx", "10u", seq)
rx = Event("rx", "50u", seq)
#rx.set_rx_phase(0)
seq.set_rx(rx, True)
@ -36,11 +41,12 @@ json = seq.to_json()
print(json)
#sim = Simulator()
sim = Simulator()
#sim.set_averages(100)
sim.set_averages(100)
# Returns the data at the RX event
#result = sim.run(seq)
result = sim.run_sequence(seq)
#result.plot()
plt.plot(result.tdx, abs(result.tdy))
plt.show()