Public Access
move to a more standard python packaging structure
This commit is contained in:
1 parent
1322fd3835
commit
6abb338c4a
43 files changed
+245
-83
No files matched your search
@@ -0,0 +1,768 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import types
|
||||
import numpy
|
||||
|
||||
class StateBase(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
def to_xml(self, indent = ""):
|
||||
return indent + "<!-- " + repr(self) + " -->"
|
||||
|
||||
class StateSimple(StateBase):
|
||||
def __init__(self, time, content=None):
|
||||
super(StateSimple, self).__init__()
|
||||
if time < 0:
|
||||
raise AssertionError("time for state is negative!")
|
||||
self.time = time
|
||||
self.content = content
|
||||
|
||||
def to_xml(self, indent = ""):
|
||||
s = indent + '<state time="%s"' % repr(self.time)
|
||||
if self.content is None:
|
||||
return s + '/>\n'
|
||||
s += '>\n'
|
||||
s += indent + ' ' + str(self.content) + '\n'
|
||||
s += indent + '</state>\n'
|
||||
return s
|
||||
def __repr__(self):
|
||||
return 'StateSimple(%s, %s)' % (self.time, repr(self.content))
|
||||
|
||||
class StateList(StateBase):
|
||||
def __init__(self):
|
||||
super(StateList, self).__init__()
|
||||
self.list = []
|
||||
def to_xml(self, indent = " "):
|
||||
s = ""
|
||||
for k in self.list:
|
||||
if hasattr(k, "to_xml"):
|
||||
s += k.to_xml(indent)
|
||||
else:
|
||||
s += indent + str(k)
|
||||
return s
|
||||
def append(self, val):
|
||||
self.list.append(val)
|
||||
|
||||
|
||||
class StateLoop(StateList):
|
||||
"""Represents a loop in the state tree"""
|
||||
def __init__(self, repeat):
|
||||
super(StateLoop, self).__init__()
|
||||
self.repeat = repeat
|
||||
def to_xml(self, indent = ""):
|
||||
s = indent + ('<sequent repeat="%d">\n' % self.repeat)
|
||||
s += super(StateLoop, self).to_xml(indent + " ")
|
||||
s += indent + '</sequent>\n'
|
||||
return s
|
||||
def __repr__(self):
|
||||
return 'StateLoop(repeat=%d, %s)' \
|
||||
% (self.repeat, repr(self.list))
|
||||
|
||||
|
||||
#############################################################
|
||||
# #
|
||||
# Class: Experiment #
|
||||
# #
|
||||
# Purpose: Represents one full experiment (one program on #
|
||||
# the pulse-card; one file) #
|
||||
# #
|
||||
#############################################################
|
||||
from . import dac
|
||||
|
||||
class Experiment:
|
||||
"""
|
||||
Class holding the complete state tree for a single experiment. This state tree represents one
|
||||
program on the pulse card. It is written in a single file and picked up by the backend.
|
||||
|
||||
:param Gating gating: gate length in s
|
||||
:param list rf_sources: list of rf sources ttl channels
|
||||
:param list rf_gates: list of rf gates ttl channels len(rf_gates) == len(rf_sources)
|
||||
|
||||
"""
|
||||
job_id = 0
|
||||
|
||||
def __init__(self, gating=None, rf_sources=[], rf_gates=[]):
|
||||
self.job_id = Experiment.job_id
|
||||
Experiment.job_id += 1
|
||||
|
||||
self.state_list = StateList()
|
||||
self.list_stack = []
|
||||
self.description = { }
|
||||
self.gating = gating
|
||||
assert type(rf_sources) == type(list()), "rf_sources needs to be a list with the channels"
|
||||
assert type(rf_gates) == type(list()), "rf_gates needs to be a list with the channels"
|
||||
assert len(rf_gates) == len(rf_sources), "rf_sources and rf_gates must have equal number of entries"
|
||||
self.rf_sources = rf_sources
|
||||
self.rf_gates = rf_gates
|
||||
|
||||
#for tracking the experiment length:
|
||||
#because loops are possible we need to track the length for each loop level
|
||||
self.total_time=[]
|
||||
self.total_time.append(0.0)
|
||||
|
||||
#and we need to know the number of iterations of the loops to multiply the state.
|
||||
self.loop_iterations=[]
|
||||
self.loop_iterations.append(1)
|
||||
|
||||
# Commands -------------------------------------------------------------------------------------
|
||||
|
||||
def ttl_pulse(self, length, channel = None, value = None):
|
||||
"""
|
||||
Creates a state with length **length** and switches requested bits of the pulse programmer to HIGH.
|
||||
|
||||
This command generates a TTL pulse with the specified duration on a specific channel or multiple channels.
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
length : float
|
||||
Pulse length in seconds.
|
||||
channel : int, optional
|
||||
Selects a single channel (No. 1 - 24). If provided, the value is calculated as 2^channel.
|
||||
value : int, optional
|
||||
Lines to set (integer). For example, value=3 selects channels 0 and 1 (2**0 + 2**1).
|
||||
If both channel and value are None, the pulse is set to 0.
|
||||
|
||||
Examples:
|
||||
---------
|
||||
>>> e.ttl_pulse(length=5e-6, channel=1)
|
||||
Creates a 5 microsecond pulse on channel 1.
|
||||
|
||||
>>> e.ttl_pulse(length=3e-6, value=3)
|
||||
Creates a 3 microsecond pulse on channels 0 and 1.
|
||||
|
||||
>>> e.ttl_pulse(length=1e-6, value=0xffffff)
|
||||
Creates a 1 microsecond pulse on all channels.
|
||||
|
||||
Notes:
|
||||
------
|
||||
Hexadecimal representation (number starts with *0x*) is convenient as the numbers are shorter.
|
||||
To set all channels, value would be in decimal 16777215 and in hexadecimal 0xffffff.
|
||||
One letter in hexadecimal represents four bits.
|
||||
|
||||
See Also:
|
||||
---------
|
||||
ttls : Same as ttl_pulse, but no channel keyword.
|
||||
"""
|
||||
the_value=0
|
||||
if value is not None:
|
||||
the_value=int(value)
|
||||
elif channel is not None:
|
||||
the_value=1<<channel
|
||||
self.state_list.append(StateSimple(length, \
|
||||
'<ttlout value="0x%06x"/>' % the_value))
|
||||
|
||||
self.total_time[-1] += length
|
||||
|
||||
## Same as ttl_pulse, but no *channel* keyword
|
||||
def ttls(self, length = None, value = None):
|
||||
"""
|
||||
Same as ttl_pulse, but no *channel* keyword
|
||||
|
||||
:param float length:
|
||||
:param int value: lines to set (integer)
|
||||
:return:
|
||||
"""
|
||||
the_value=int(value)
|
||||
s_content = '<ttlout value="0x%06x"/>' % the_value
|
||||
if length is not None:
|
||||
self.state_list.append(StateSimple(length, s_content))
|
||||
self.total_time[-1] += length
|
||||
else:
|
||||
self.state_list.append(s_content)
|
||||
|
||||
|
||||
def rf_pulse(self, length=None, phase=0, source=0):
|
||||
"""
|
||||
Make an rf pulse, including gating and phase switching. Only possible if Experiment is configured.
|
||||
|
||||
:param float length: pulse length
|
||||
:param float phase: pulse phase
|
||||
:param int source: source id
|
||||
"""
|
||||
if not self.gating:
|
||||
raise SyntaxError("Can not use rf_pulse without configuration: Experiment(gating=None, rf_sources=[], rf_gates=[]")
|
||||
self.set_phase(phase, ttls=self.rf_gates[source])
|
||||
self.ttls(length=self.gating-0.5e-6,value=self.rf_gates[source])
|
||||
self.ttls(length=length, value=self.rf_gates[source]+self.rf_sources[source])
|
||||
|
||||
## Beginning of a new state
|
||||
def state_start(self, time):
|
||||
"""
|
||||
Starts a state in the pulse programs with duration *time*.
|
||||
This must be closed with :func:`state_end`.
|
||||
|
||||
Typically only used in very special cases.
|
||||
"""
|
||||
self.state_list.append('<state time="%s">\n' % repr(time))
|
||||
self.total_time[-1] += time
|
||||
|
||||
## End of *state_start*
|
||||
def state_end(self):
|
||||
"""
|
||||
Closes a previous :func:`state_start`
|
||||
"""
|
||||
self.state_list.append('</state>\n')
|
||||
|
||||
def wait(self, time, ttls=None, gating=False):
|
||||
"""
|
||||
Waits for a specified amount of time without performing any actions.
|
||||
|
||||
This command inserts a delay in the pulse sequence, allowing time for relaxation,
|
||||
or synchronization with other events.
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
time : float
|
||||
Time to wait in seconds.
|
||||
The minimum time is 90 ns, and the maximum is essentially unlimited (years).
|
||||
The backend driver circumvents the limit imposed by the pulse programmer by adding loops.
|
||||
|
||||
ttls : int, optional
|
||||
Additional TTL lines to set (integer) during the wait period.
|
||||
Allows simultaneous control of TTL lines while waiting.
|
||||
For example, ttls=3 activates channels 0 and 1 (2^0 + 2^1).
|
||||
|
||||
gating : bool, optional
|
||||
If True, reduces the wait time by the gating time (typically 2 µs).
|
||||
This is useful for waiting in front of an rf_pulse to account for gating delays.
|
||||
Default is False.
|
||||
|
||||
Returns:
|
||||
--------
|
||||
None
|
||||
|
||||
Examples:
|
||||
---------
|
||||
>>> e.wait(time=2e-3)
|
||||
Waits for 2 milliseconds.
|
||||
|
||||
>>> e.wait(time=1e-6, ttls=3)
|
||||
Waits for 1 microsecond while activating TTL channels 0 and 1.
|
||||
|
||||
>>> e.wait(time=5e-6, gating=True)
|
||||
Waits for 5 microseconds, reduced by the gating time for rf_pulse synchronization.
|
||||
|
||||
"""
|
||||
if gating:
|
||||
time -= self.gating
|
||||
if ttls is not None:
|
||||
s_content = '<ttlout value="0x%06x"/>' % ttls
|
||||
self.state_list.append(StateSimple(time,s_content))
|
||||
else:
|
||||
self.state_list.append(StateSimple(time))
|
||||
|
||||
self.total_time[-1] += time
|
||||
|
||||
|
||||
def record(self, samples, frequency, timelength=None, sensitivity = None, ttls=None, channels = 3, offset = None, impedance = None):
|
||||
"""
|
||||
Records data with a given number of samples, sampling frequency, and sensitivity.
|
||||
This command starts data acquisition from the ADC (Analog-to-Digital Converter).
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
samples : int
|
||||
Number of samples to record. This determines the number of data points in the resulting signal.
|
||||
|
||||
frequency : float
|
||||
Sampling frequency in Hz. This determines how often samples are taken from the analog signal.
|
||||
The maximum sampling frequency is 20 MHz.
|
||||
|
||||
timelength : float, optional
|
||||
Length of this state in seconds. If not specified (None), it is calculated automatically as samples/frequency.
|
||||
This parameter allows overriding the automatic calculation for special timing requirements.
|
||||
|
||||
sensitivity : float or list, optional
|
||||
Sensitivity in U_MAX/V. Specifies the input voltage range for the ADC.
|
||||
Accepted values are 0.2, 0.5, 1, 2, 5, and 10 V.
|
||||
Can be a single value (applied to all channels) or a list of values (one per channel).
|
||||
|
||||
ttls : int, optional
|
||||
Additional TTL lines to set (integer) during recording. Allows simultaneous control of TTL lines.
|
||||
For example, ttls=3 activates channels 0 and 1 (2^0 + 2^1).
|
||||
|
||||
channels : int, optional
|
||||
Channels to activate. Default is 3 (channels 0 and 1).
|
||||
Accepted values are 1 (channel 0), 3 (channels 0 and 1), 5 (channels 0, 1, and 2), and 15 (all 4 channels).
|
||||
|
||||
offset : int or list, optional
|
||||
Voltage offset for the ADC input. Can be a single integer value (applied to all channels)
|
||||
or a list of values (one per channel).
|
||||
Normally not used.
|
||||
|
||||
impedance : float or list, optional
|
||||
Input impedance for the ADC. Can be a single number (applied to all channels)
|
||||
or a list of values (one per channel).
|
||||
Normally set in the backend config and not changeable.
|
||||
|
||||
Returns:
|
||||
--------
|
||||
None
|
||||
|
||||
Examples:
|
||||
---------
|
||||
>>> e.record(samples=1024, frequency=2e6, sensitivity=2)
|
||||
Records a signal with 1024 data points and 2 MHz sampling frequency.
|
||||
The sensitivity will be ±2 V, providing a resolution of 0.2 mV with a 14-bit ADC card.
|
||||
|
||||
>>> e.record(samples=4096, frequency=10e6, sensitivity=[1, 2], channels=3)
|
||||
Records a signal with 4096 data points and 10 MHz sampling frequency.
|
||||
Channel 0 uses 1 V sensitivity, and channel 1 uses 2 V sensitivity.
|
||||
|
||||
>>> e.record(samples=2048, frequency=5e6, ttls=3)
|
||||
Records a signal with 2048 data points and 5 MHz sampling frequency.
|
||||
Simultaneously activates TTL channels 0 and 1.
|
||||
|
||||
Notes:
|
||||
------
|
||||
- Multiple record statements can be in a single scan (gated sampling) or in a loop.
|
||||
- The onboard memory can hold 8M samples shared by all channels (depends on the board).
|
||||
- The sensitivity setting affects the resolution of the ADC.
|
||||
- The actual timing may vary slightly due to hardware limitationslike pre- and post-trigger delays.
|
||||
|
||||
"""
|
||||
attributes='s="%d" f="%d"'%(samples,frequency)#%g
|
||||
if channels != 1 and channels != 3 and channels != 5 and channels != 15:
|
||||
raise ValueError("Channel definition is illegal")
|
||||
attributes += ' channels="%i"'%(channels)
|
||||
|
||||
nchannels = 0
|
||||
if channels == 1:
|
||||
nchannels = 1
|
||||
elif channels == 3 or channels == 5:
|
||||
nchannels = 2
|
||||
elif channels == 15:
|
||||
nchannels = 4
|
||||
if sensitivity is not None:
|
||||
# float values are allowed and applied to all channels
|
||||
if isinstance(sensitivity, float) or isinstance(sensitivity, int):
|
||||
for i in range(nchannels):
|
||||
attributes +=' sensitivity%i="%f"'%(i, float(sensitivity))
|
||||
else:
|
||||
for i in range(nchannels):
|
||||
attributes +=' sensitivity%i="%f"'%(i, sensitivity[i])
|
||||
if offset is not None:
|
||||
# int values are allowed and applied to all channels
|
||||
if isinstance(offset, int):
|
||||
for i in range(nchannels):
|
||||
attributes +=' offset%i="%f"'%(i, offset)
|
||||
else:
|
||||
for i in range(nchannels):
|
||||
attributes +=' offset%i="%f"'%(i, offset[i])
|
||||
if impedance is not None:
|
||||
# float values are allowed and applied to all channels
|
||||
if isinstance(impedance, float):
|
||||
for i in range(nchannels):
|
||||
attributes += ' impedance%i="%i"'%(i, impedance)
|
||||
else:
|
||||
for i in range(nchannels):
|
||||
attributes += ' impedance%i="%i"'%(i, impedance[i])
|
||||
|
||||
s_content = '<analogin %s/>' % attributes
|
||||
if ttls is not None:
|
||||
s_content+='<ttlout value="0x%06x"/>' % ttls
|
||||
if timelength is None:
|
||||
timelength = samples / float(frequency)#*1.01
|
||||
self.state_list.append(StateSimple(timelength, s_content))
|
||||
self.total_time[-1] += timelength
|
||||
|
||||
## Create a loop on the pulse programmer. Loop contents can not change inside the loop.
|
||||
# @params iterations Number of loop iterations
|
||||
def loop_start(self, iterations):
|
||||
"""
|
||||
creates a loop of given number of iterations and has to be closed by loop_end().
|
||||
Commands inside the loop can not change, i.e., the parameters are the same for each loop run.
|
||||
This loop is created on the pulse programmer, thus saving commands.
|
||||
Close the loop with :func:`loop_end`.
|
||||
|
||||
:param int iterations: number of iterations to loop
|
||||
"""
|
||||
l = StateLoop(iterations)
|
||||
self.state_list.append(l)
|
||||
# (These two lines could probably be guarded by a mutex)
|
||||
self.list_stack.append(self.state_list)
|
||||
self.state_list = l
|
||||
|
||||
self.total_time.append(0.0)
|
||||
self.loop_iterations.append(iterations)
|
||||
|
||||
## End loop state
|
||||
def loop_end(self):
|
||||
"""
|
||||
Closes a l:func:`loop_start` statement.
|
||||
"""
|
||||
# (This line could probably be guarded by a mutex)
|
||||
self.state_list = self.list_stack.pop(-1)
|
||||
|
||||
looptime=self.total_time.pop(-1)
|
||||
loopiterations=self.loop_iterations.pop(-1)
|
||||
self.total_time[-1] += looptime*loopiterations
|
||||
|
||||
|
||||
def set_frequency(self, frequency, phase, ttls=0):
|
||||
"""
|
||||
Sets the frequency and phase of the frequency source and optionally further channels.
|
||||
The state length is 2 µs. Stabilisation time depends on RF source.
|
||||
|
||||
:param float frequency: frequency to set in Hz
|
||||
:param float phase: phase to set
|
||||
:param int ttls: default=0, additional ttl lines to set (integer)
|
||||
"""
|
||||
s_content = '<analogout id="0" f="%f" phase="%f"/>' % (frequency, phase)
|
||||
if ttls != 0:
|
||||
s_content += '<ttlout value="0x%06x"/>' % ttls
|
||||
self.state_list.append(StateSimple(2e-6, s_content))
|
||||
|
||||
self.total_time[-1] += 2e-6
|
||||
|
||||
def set_pfg(self, dac_value=None, length=None, shape=('rec', 0), trigger=4, is_seq=False):
|
||||
"""
|
||||
This sets the value for the PFG, it also sets it back to dac_value=0 automatically.
|
||||
If you don't wish to do so (i.e. line shapes) set is_seq=1
|
||||
If you want to set a trigger, set trigger (default=4, i.e. channel 2)
|
||||
If you want shaped gradients: shape=(ashape, resolution), ashape can be rec, sin2, sin
|
||||
|
||||
The default DAC ID is hardcoded (id=1)!
|
||||
|
||||
:param int dac_value: DAC value to set
|
||||
:param float length: Duration of the state, minimum length is 42*90ns=3.78us (default)
|
||||
:param tuple shape: Tuple of (shape, resolution/seconds), shape can be one of: rec (default), sin2, sin
|
||||
:param bool is_seq: If set to *True*, do *NOT* set DAC to zero at the end of this state
|
||||
:param int trigger: default=4, lines to set (integer)
|
||||
"""
|
||||
try:
|
||||
form, resolution = shape
|
||||
except:
|
||||
raise SyntaxError("shape argument needs to be a tuple, i.e. ('shape',resolution), shape can be sin, sin2, rec")
|
||||
|
||||
if length == None:
|
||||
# mimimum length
|
||||
length=42*9e-8
|
||||
if resolution >= length:
|
||||
raise ValueError("Resolution %.3e of shaped gradients can not be longer than total length %.3e"%(resolution, length))
|
||||
if resolution < 42*9e-8:
|
||||
raise ValueError("Resulution %.3e can not be smaller than %.3e"%(resolution, 42*9e-8))
|
||||
|
||||
t_steps = numpy.arange(0,length,resolution)
|
||||
|
||||
if form == 'rec': # shape==None --> rectangular gradients
|
||||
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac_value)
|
||||
self.state_list.append(StateSimple(length, s_content))
|
||||
self.total_time[-1] += length
|
||||
|
||||
if not is_seq:
|
||||
s_content = '<analogout id="1" dac_value="0"/>'
|
||||
self.state_list.append(StateSimple(42*9e-8, s_content))
|
||||
|
||||
self.total_time[-1] += 42*9e-8
|
||||
|
||||
elif form == 'sin2':
|
||||
# sin**2 shape
|
||||
for t in t_steps:
|
||||
dac = int (dac_value*numpy.sin(numpy.pi/length*t)**2)
|
||||
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac)
|
||||
self.state_list.append(StateSimple(resolution, s_content))
|
||||
|
||||
# set it back to zero
|
||||
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="0"/>' % (trigger)
|
||||
self.state_list.append(StateSimple(resolution, s_content))
|
||||
|
||||
self.total_time[-1] += resolution*(len(t_steps)+1)
|
||||
|
||||
elif form == 'sin':
|
||||
# sin shape
|
||||
for t in t_steps:
|
||||
dac = int (dac_value*numpy.sin(numpy.pi/length*t))
|
||||
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac)
|
||||
self.state_list.append(StateSimple(resolution, s_content))
|
||||
# set it back to zero
|
||||
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="0"/>' % (trigger)
|
||||
self.state_list.append(StateSimple(resolution, s_content))
|
||||
|
||||
self.total_time[-1] += resolution*(len(t_steps)+1)
|
||||
else: # don't know what to do
|
||||
raise SyntaxError("form is unknown: %s"%form)
|
||||
|
||||
def set_dac(self, dac_value, dac_id=1, length=None, is_seq=False, ttls=0):
|
||||
"""
|
||||
Sets the value for the DAC (Digital-to-Analog Converter) and optionally additional TTL lines.
|
||||
This command is used to control analog output signals, such as pulsed field gradients.
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
dac_value : int
|
||||
DAC value, between -2**19-1 (-524287) and +2**19 (524288).
|
||||
This value determines the analog output voltage.
|
||||
|
||||
dac_id : int, optional
|
||||
Specifies which DAC to control. Default is 1.
|
||||
This allows control of multiple DAC channels if available.
|
||||
|
||||
length : float, optional
|
||||
Length of this state in seconds. Default is None, which sets the length to 42*90ns=3.78µs.
|
||||
If is_seq is False, the total length is doubled due to the automatic reset to zero.
|
||||
|
||||
is_seq : bool, optional
|
||||
If True, the DAC value is not reset to zero after the pulse. Default is False.
|
||||
Use is_seq=True for creating line shapes or sequential pulses.
|
||||
|
||||
ttls : int, optional
|
||||
Lines to set (integer) for TTL output. Default is 0.
|
||||
Allows simultaneous control of TTL lines along with the DAC.
|
||||
|
||||
Returns:
|
||||
--------
|
||||
None
|
||||
|
||||
Examples:
|
||||
---------
|
||||
>>> e.set_dac(dac_value=15040, dac_id=1, length=1e-3)
|
||||
Sets DAC channel 1 to value 15040 for 1 millisecond and then resets to zero.
|
||||
|
||||
>>> e.set_dac(dac_value=10000, is_seq=True)
|
||||
Sets DAC to value 10000 for 3.78µs without resetting to zero.
|
||||
|
||||
>>> e.set_dac(dac_value=20000, ttls=3)
|
||||
Sets DAC to value 20000 and activates TTL channel 1 (2^1 + 2^0 = 3).
|
||||
|
||||
Notes:
|
||||
------
|
||||
- The minimum state length is 3.78 µs when is_seq=True.
|
||||
- When is_seq=False, the DAC is automatically reset to zero, adding another 3.78 µs to the total length.
|
||||
- The actual length may be longer than specified if additional time is required for DAC settling.
|
||||
|
||||
"""
|
||||
if length==None:
|
||||
length=42*9e-8
|
||||
s_content = '<analogout id="%d" dac_value="%i"/><ttlout value="0x%06x"/>' \
|
||||
% (dac_id, dac_value, ttls)
|
||||
self.state_list.append(StateSimple(length, s_content))
|
||||
|
||||
self.total_time[-1] += length
|
||||
|
||||
if not is_seq:
|
||||
s_content = '<analogout id="%d" dac_value="0"/><ttlout value="0x%06x"/>' \
|
||||
% (dac_id, ttls)
|
||||
self.state_list.append(StateSimple(42*9e-8, s_content))
|
||||
|
||||
self.total_time[-1] += 42*9e-8
|
||||
|
||||
def set_phase(self, phase, ttls=0):
|
||||
"""
|
||||
Sets the phase of the RF source to the specified value.
|
||||
|
||||
This command changes the phase of the frequency source. The phase is given in degrees and is
|
||||
relative to the phase at the beginning of the experiment.
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
phase : float
|
||||
Phase to set in degrees. This value determines the phase of the RF signal.
|
||||
|
||||
ttls : int, optional
|
||||
Lines to set (integer) for TTL output. Default is 0.
|
||||
Allows simultaneous control of TTL lines along with setting the phase.
|
||||
|
||||
Returns:
|
||||
--------
|
||||
None
|
||||
|
||||
Examples:
|
||||
---------
|
||||
>>> e.set_phase(phase=90)
|
||||
Sets the receiver phase to 90 degrees.
|
||||
|
||||
>>> e.set_phase(phase=180, ttls=3)
|
||||
Sets the phase to 180 degrees and activates TTL channels 0 and 1.
|
||||
|
||||
>>> e.set_phase(phase=45)
|
||||
Sets the phase to 45 degrees.
|
||||
|
||||
Notes:
|
||||
------
|
||||
- The state length for this command is 0.5 µs.
|
||||
- The stabilization time for the phase change is RF source dependent. A typical value for PTS310 is 2 µs.
|
||||
- The phase is relative to the initial phase set at start of the experiment.
|
||||
"""
|
||||
s_content = '<analogout phase="%f" />' % (phase)
|
||||
if ttls!=0:
|
||||
s_content += '<ttlout value="%d"/>' % ttls
|
||||
self.state_list.append(StateSimple(0.5e-6, s_content))
|
||||
|
||||
self.total_time[-1] += 0.5e-6
|
||||
|
||||
|
||||
def set_description(self, key, value):
|
||||
"""
|
||||
Sets a description key-value pair in the experiment description dictionary.
|
||||
|
||||
This command creates an entry with the specified key and value in the description dictionary.
|
||||
In case of data being stored in a HDF5 file, this dictionary is stored as well, allowing
|
||||
parameter passing between the experiment script and the result script.
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
key : str
|
||||
The key identifier for the description entry. This will be used to retrieve the value later.
|
||||
|
||||
value : any
|
||||
The value to be associated with the key. Can be of any type (string, number, list, etc.).
|
||||
|
||||
Returns:
|
||||
--------
|
||||
None
|
||||
|
||||
Examples:
|
||||
---------
|
||||
>>> e.set_description(key="tau", value=2e-3)
|
||||
Sets the description entry with key "tau" to the value 0.002 seconds.
|
||||
|
||||
>>> e.set_description(key="temperature", value=298.15)
|
||||
Sets the description entry with key "temperature" to the value 298.15 Kelvin.
|
||||
|
||||
>>> e.set_description(key="pulse_sequence", value="CPMG")
|
||||
Sets the description entry with key "pulse_sequence" to the string "CPMG".
|
||||
|
||||
Notes:
|
||||
------
|
||||
If the key already exists in the description dictionary, a warning message will be printed
|
||||
indicating that the existing value will be overwritten.
|
||||
"""
|
||||
if key in list(self.description.keys()):
|
||||
print('Warning: Overwriting existing description "%s" = "%s" with "%s"' % (key, self.description[key], value))
|
||||
self.description[key] = value
|
||||
|
||||
def set_pts_local(self):
|
||||
"""
|
||||
Sets the PTS310/PTS500 frequency source to local mode.
|
||||
State length is 2 µs.
|
||||
"""
|
||||
self.state_list.append(StateSimple(1e-6, '<ttlout value="0xf000"/>'))
|
||||
self.state_list.append(StateSimple(1e-6, '<ttlout value="0x8000"/>'))
|
||||
|
||||
self.total_time[-1] += 2e-6
|
||||
|
||||
# / Commands -----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Public Methods -------------------------------------------------------------------------------
|
||||
|
||||
def get_job_id(self) -> int:
|
||||
"""
|
||||
Returns the job-id of the current experiment
|
||||
|
||||
:returns: job_id
|
||||
"""
|
||||
return self.job_id
|
||||
|
||||
|
||||
def write_xml_string(self):
|
||||
"""
|
||||
Returns the current program as a string
|
||||
|
||||
:returns: XML string
|
||||
"""
|
||||
|
||||
# Standart XML-Kopf
|
||||
xml_string = '<?xml version="1.0" encoding="ISO-8859-1"?>\n'
|
||||
|
||||
# Experiment-Start-Tag einfügen
|
||||
xml_string += '<experiment no="%d">\n' % self.job_id
|
||||
|
||||
# Descriptions einfügen
|
||||
if len(self.description)==0:
|
||||
xml_string += ' <description/>\n'
|
||||
else:
|
||||
xml_string += ' <description>\n'
|
||||
for key,value in self.description.items():
|
||||
type_string="repr"
|
||||
if value is None:
|
||||
type_string="None"
|
||||
value=""
|
||||
elif type(value) is float or isinstance(value, numpy.floating):
|
||||
type_string="Float"
|
||||
value=repr(value)
|
||||
elif type(value) is int or isinstance(value, numpy.integer):
|
||||
type_string="Int"
|
||||
value=repr(value)
|
||||
elif type(value) is int:
|
||||
type_string="Long"
|
||||
value=repr(value)
|
||||
elif type(value) is complex or isinstance(value, numpy.complexfloating):
|
||||
type_string="Complex"
|
||||
value=repr(value)
|
||||
elif type(value) is bool or isinstance(value, numpy.bool_):
|
||||
type_string="Boolean"
|
||||
value=repr(value)
|
||||
elif type(value) in (str,):
|
||||
type_string="String"
|
||||
else:
|
||||
value=repr(value)
|
||||
xml_string += ' <item key="%s" type="%s">%s</item>\n'%(key, type_string ,value)
|
||||
xml_string += " </description>\n"
|
||||
|
||||
# Experiment-Inhalt einfügen
|
||||
xml_string += self.state_list.to_xml(indent = " ")
|
||||
|
||||
# Experiment-End-Tag
|
||||
xml_string += '</experiment>\n'
|
||||
|
||||
return xml_string
|
||||
|
||||
def write_quit_job(self):
|
||||
"""
|
||||
Returns a xml quit-job
|
||||
:returns: XML quit-job
|
||||
"""
|
||||
return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit/>'
|
||||
|
||||
def get_length(self):
|
||||
timelength = 0.0
|
||||
#calculate the correct timelength also for unclosed loops, if there is no unclosed loop
|
||||
#the timelength of this experiment is self.total_time[-1].
|
||||
for i in range(len(self.total_time)):
|
||||
timelength += self.total_time[i]
|
||||
timelength *= self.loop_iterations[i]
|
||||
return timelength
|
||||
|
||||
class Quit(Experiment):
|
||||
def write_xml_string(self):
|
||||
return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit no="%d"/>'%self.job_id
|
||||
|
||||
|
||||
# /Public Methods ------------------------------------------------------------------------------
|
||||
|
||||
def self_test():
|
||||
e = Experiment()
|
||||
e.set_description("key", "value")
|
||||
e.set_frequency(85e6, 90, ttls=16)
|
||||
e.wait(1e-6)
|
||||
#e.rf_pulse(1, 1e-6/3) # val = 1
|
||||
e.ttl_pulse(1e-6/3, 1) # val = 2
|
||||
e.ttl_pulse(1e-6/3, None, 7) # val = 7
|
||||
if True:
|
||||
e.loop_start(30)
|
||||
e.set_pfg(dac_value=1024, length=5e-6, is_seq = True, shape=('rec', 42*9e-8))
|
||||
e.loop_start(400)
|
||||
e.set_phase(270, ttls = 32)
|
||||
e.loop_end()
|
||||
e.ttl_pulse(5e-6, channel = 6)
|
||||
e.loop_end()
|
||||
else:
|
||||
l = StateLoop(3)
|
||||
l.append(StateSimple(5e-6, '<ttlout value="1"/>'))
|
||||
e.state_list.append(l)
|
||||
e.set_dac(12345, dac_id=2, is_seq = True, ttls=16)
|
||||
e.record(1024, 20e6)
|
||||
try:
|
||||
e.wait(-1)
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("An exception should happen")
|
||||
e.set_pts_local()
|
||||
print(e.write_xml_string())
|
||||
|
||||
print(e.get_length())
|
||||
|
||||
if __name__ == '__main__':
|
||||
self_test()
|
||||
Reference in new issue
Block a user