setting_node#

A tree-structured container for Settings.

The SettingNode class combines a bunch of Settings together. It may also contain other SettingNodes. Together, the contents form a tree structure that provides a useful way of grouping Settings.

As an example, we manually construct a tree of SettingNodes with some dummy Settings, but it is usually not necessary. The root node in the following examples is called 'node'.

What’s inside?#

The easiest way to see the content of the node is the SettingNode.print_tree() method:

 >>> node.print_tree(levels=1)
  "root"

  ╚═ flux: "root.flux"
  ╚═ pulse: "root.pulse"

We see that the 'root' node has two children, named 'root.flux' and 'root.pulse', which themselves are also SettingNodes. This follows the typical naming convention in EXA: Subnodes include the names of their parents, separated by a dot.

 >>> node.print_tree()
  "root"

  ╠═ flux: "root.flux"
  ║   ╠─ voltage: Voltage = 1.5 V
  ║   ╚─ resistance: Resistance = None (automatic/unspecified)
  ╚═ pulse: "root.pulse"
      ╠─ amplitude: Amplitude = 1.0
      ╚─ duration: Duration = 1e-07 s

The children contain some dummy Settings, showing the keys, labels and current values.

For other ways to access the content of the node, see also SettingNode.children, SettingNode.all_settings, and SettingNode.nodes_by_type().

Get and set values#

The values within the nodes can be accessed using the attribute or dictionary syntax:

>>> node.pulse.amplitude.value
1.0
>>> node['flux']['voltage'].value
1.5

The values can be changed with a simple = syntax:

>>> node.pulse.amplitude = 1.4
>>> node.pulse.amplitude.value
1.4

Note

node.setting refers to the Setting object. node.setting.value syntax refers to the data stored inside.

SettingNode also supports “the path notation” by default (but not if align_name is set to False, since it cannot be made to work consistently if nodes are allowed to be named differently from their paths):

>>> node['flux.voltage']

is the same as node['flux']['voltage'].

Basic manipulation#

Adding and deleting new Settings and nodes is simple:

>>> modified = node.copy()
>>> del modified.flux # removes the node
>>> del modified.pulse.amplitude # removes the Setting
>>> modified.pulse.my_new_setting = Setting(Parameter('my name'), 33)

It is usually a good idea to make a copy of the original node, so that it won’t be modified accidentally.

The path notation of ``SettingNode``also works when inserting:

>>> node['flux.my.new.path.foo'] = Setting(Parameter('foo'), 1.0)

Any nodes that did not already exist under node will be inserted (in this case flux already existed, but the rest not, so under flux the nodes my, new, and path would be added), and then finally the value is added as child to the final node. Note: SettingNode always alings the path and name of any nodes under it, so this would result in the new setting being renamed as “flux.my.new.path.foo”:

>>> node['flux.my.new.path.foo'] = Setting(Parameter('bar'), 1.0)

If align_name is set to False", the name and path of nodes are not automatically aligned, but otherwise the above path notation will still work. The added nodes will be named by just their path fragments ("my", "new", "path", and so on), and the Setting will be added under the key "foo", but it will still retain its name "bar". Note: the root node name will always be excluded from the paths (and names when they are aligned with the path), so that the path of ``root.foo.bar is "foo.bar".

To merge values of two SettingNodes, there are helpers SettingNode.merge() and SettingNode.merge_values().

The first one merges the tree structure and values of two nodes and outputs a third one as a result. None values are always replaced by a proper value if such exists. In case of conflicting nodes or values, the content of the first argument takes priority.

>>> result = SettingNode.merge(node.flux, node.pulse)
>>> result.print_tree()
 "root.flux"
 ╠─ amplitude: Amplitude = 1.4
 ╠─ duration: Duration = 1e-07 s
 ╚─ voltage: Voltage = 1.5 V

Note how the result has values from node.flux, but also settings node.pulse that do not exist in node.flux.

The SettingNode.merge_values() method is an in-place operation that only changes the values of Settings that already exist in the node, if possible:

>>> modified = node.copy()
>>> modified.flux.voltage = 222
>>> modified.flux.resistance = 333
>>> node.merge_values(modified, prioritize_other=True)
>>> node.print_tree()
 "root"

 ╠═ flux: "root.flux"
 ║   ╠─ voltage: Voltage = 222 V
 ║   ╚─ resistance: Resistance = 333 Ohm
 ╚═ pulse: "root.pulse"
     ╠─ amplitude: Amplitude = 1.4
     ╚─ duration: Duration = 1e-07 s

Sometimes, it is easier to collect values in a dictionary and set them all at once by using SettingNode.set_from_dict(). The nested structure of the dictionary should match the structure of the SettingNode. Keys that are not found in the tree are silently ignored, unless the strict flag is used.

>>> values_to_set = {'flux': {'resistance': 0.001}, 'ignored_entry': 234}
>>> node.set_from_dict(values_to_set)
>>> node.flux.print_tree()
 "root.flux"
 ╠─ voltage: Voltage = 222 V
 ╚─ resistance: Resistance = 0.001 Ohm

Full path: exa.common.data.setting_node

Classes

SettingNode

A tree-structured Setting container.

Inheritance

Inheritance diagram of exa.common.data.setting_node