ParameterNode guide¶
ParameterNode
is a container that can group parameters, and also other ParameterNodes
.ParameterNode
can be viewed as the object, and the Parameters
as the attributes.One distinction with is that ParameterNodes can be nested, and so another way to look at a ParameterNode is as a dictionary, where its items can either be other dictionaries (ParameterNodes), or values (Parameters). It is therefore recommended to have a basic understanding of Python dictionaries, see e.g. https://www.w3schools.com/python/python_dictionaries.asp
ParameterNode
is an Instrument
, which corresponds to a physical instrument.ParameterNode
does not need to be limited to actual instruments.ParameterNode with Parameters¶
[ ]:
from qcodes import ParameterNode, Parameter
node = ParameterNode('node')
node.p = Parameter(set_cmd=None, initial_value=1) # Can also use node.add_parameter('p', set_cmd=None)
print('node.p.name =', node.p.name)
node.p.name = p
The Parameter
is registered in the ParameterNode’s attribute parameters
:
[ ]:
node.parameters
{'p': <qcodes.instrument.parameter.Parameter: p at 1929329519528>}
Similarly, ParameterNode
is added as the parent of the Parameter
:
[ ]:
node.p.parent
ParameterNode node containing 1 parameters
This is also reflected when we get a string representation of the parameter:
[ ]:
str(node.p)
'node_p'
Note: Once a Parameter
is added to a ParameterNode
, adding the Parameter
to other ParameterNodes
does not change it’s parent
ParameterNode containing ParameterNodes¶
A ParameterNode can also contain other ParameterNodes:
[ ]:
node.subnode = ParameterNode()
node.subnode.p = Parameter(set_cmd=None)
str(node.subnode.p)
'node_subnode_p'
Simplified snapshotting¶
[ ]:
node.snapshot()
{'__class__': 'qcodes.instrument.parameter_node.ParameterNode',
'functions': {},
'name': 'node',
'parameter_nodes': {'subnode': {'__class__': 'qcodes.instrument.parameter_node.ParameterNode',
'functions': {},
'name': 'subnode',
'parameter_nodes': {},
'parameters': {'p': {'__class__': 'qcodes.instrument.parameter.Parameter',
'full_name': 'node_subnode_p',
'label': 'P',
'name': 'p',
'raw_value': None,
'ts': None,
'value': None}},
'submodules': {}}},
'parameters': {'p': {'__class__': 'qcodes.instrument.parameter.Parameter',
'full_name': 'node_p',
'label': 'P',
'name': 'p',
'raw_value': 1,
'ts': '2018-11-22 17:23:15',
'value': 1}},
'submodules': {}}
simplify_snapshot
:[ ]:
node.simplify_snapshot = True
node.subnode.simplify_snapshot = True
[ ]:
node.snapshot()
{'__class__': 'qcodes.instrument.parameter_node.ParameterNode',
'p': 1,
'subnode': {'__class__': 'qcodes.instrument.parameter_node.ParameterNode',
'p': None}}
We can also print a snapshot of all parameters and parameter nodes:
[ ]:
node.print_snapshot()
node :
parameter value
--------------------------------------------------------------------------------
p : 1
node_subnode :
parameter value
--------------------------------------------------------------------------------
p : None
Accessing Parameters as attributes¶
[ ]:
node = ParameterNode()
node.p = Parameter(set_cmd=None)
node.p(42) # Setting value
node.p() # Getting value
42
Compare this to a standard python class, where the attribute behaves just like the variable it represents
[ ]:
class C:
pass
c = C()
c.p = 1 # Setting value
c.p # Getting value
1
ParameterNodes
and Parameters
.use_as_atttributes
to a ParameterNode:[ ]:
node = ParameterNode(use_as_attributes=True)
node.p = Parameter(set_cmd=None)
node.p = 42 # Setting value
node.p # Getting value
42
[ ]:
print(repr(node['p']))
node['p'].snapshot()
<qcodes.instrument.parameter.Parameter: p at 1929470616296>
{'__class__': 'qcodes.instrument.parameter.Parameter',
'full_name': 'p',
'label': 'P',
'name': 'p',
'raw_value': 42,
'ts': '2018-11-22 17:23:18',
'value': 42}
use_as_attributes
can have unintended consequences, as every call to the attribute will trigger it’s get
function.[ ]:
node = ParameterNode(use_as_attributes=True)
node.p = Parameter(get_cmd=lambda: print('Expensive get command called'))
for k in range(5):
print(node.p)
Expensive get command called
None
Expensive get command called
None
Expensive get command called
None
Expensive get command called
None
Expensive get command called
None
use_as_attributes=False
to ensure that we don’t accidentally perform actions on instruments that may negatively impact the experiment.use_as_attributes=True
is preferred.Defining Parameter properties via the ParameterNode¶
In more advanced cases, a ParameterNode contains parameters whose get/set need While in principle these functions can be passed
As an example, lets say we have a Pulse
class with a start time t_start
and stop time t_stop
:
[ ]:
class Pulse(ParameterNode):
def __init__(self, t_start, t_stop, **kwargs):
super().__init__(use_as_attributes=True, **kwargs)
self.t_start = Parameter(set_cmd=None, initial_value=t_start)
self.t_stop = Parameter(set_cmd=None, initial_value=t_stop)
def __repr__(self):
return f'Pulse(t_start={self.t_start}, t_stop={self.t_stop})'
Pulse(1, 3)
Pulse(t_start=1, t_stop=3)
duration = t_stop - t_start
.duration
, we want t_start
to remain fixed, and t_stop
should be changed accordingly.duration
depends on two other parameters.A basic solution would be also instantiate the duration
parameter with get_cmd and set_cmd, as such:
self.duration = Parameter(get_cmd=lambda: self.t_stop - self.t_start,
set_cmd=lambda duration: setattr(self, 't_stop', self.t_start + duration))
While this in principle works, it fails for functions that are not one-liners, plus using lambda functions can have unintended consequences. Other solutions are to either define the get/set functions elsewhere, or to subclass the Parameter and explicitly defining a get_raw
and set_raw
method. Both these options are not ideal, one of the reasons being that it obfuscates code.
As an alternative, we can define the get/set of the Parameter as methods in the ParameterNode:
[ ]:
from qcodes.instrument.parameter_node import parameter
class Pulse(ParameterNode):
def __init__(self, t_start, t_stop, **kwargs):
super().__init__(use_as_attributes=True, **kwargs)
self.t_start = Parameter(set_cmd=None, initial_value=t_start)
self.t_stop = Parameter(set_cmd=None, initial_value=t_stop)
self.duration = Parameter()
@parameter
def duration_get(self, parameter):
return self.t_stop - self.t_start
@parameter
def duration_set(self, parameter, duration):
self.t_stop = self.t_start + duration
def __repr__(self):
return f'Pulse(t_start={self.t_start}, t_stop={self.t_stop}, duration={self.duration})'
pulse = Pulse(1, 3)
pulse
Pulse(t_start=1, t_stop=3, duration=2)
[ ]:
pulse.duration = 5
pulse
Pulse(t_start=1, t_stop=6, duration=5)
@parameter
to define that the method belongs to a Parameter, and the method should have the form {parameter_name}_{function}
, where {function}
can be get/set/get_parser/set_parser/vals