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