Parameter Manager#
The Parameter Manager is instrumentserver’s flagship Virtual Instrument: a hierarchical, persistent, profile-aware store of experiment parameters. It lives entirely in the Server, has no hardware behind it, and exists to be the single source of truth for the numbers of your experiment: which frequency each qubit uses, which gain each amplifier runs at, which LO every receiver shares.
You get one onto a Server like any other instrument, through a Client:
>>> from instrumentserver.client import Client
>>> cli = Client()
>>> pm = cli.find_or_create_instrument(
... "parameter_manager",
... "instrumentserver.params.ParameterManager",
... )
Everything the Parameter Manager can do works from Python and from its GUI alike, and the two stay in sync live. This page walks through the pieces in order: plain hierarchical parameters, Types, Locks, Type Locks and Globals, profile files, the GUI, and a full measurement-script example at the end. Each section is a fresh start: the snippets assume an empty Parameter Manager, so the outputs are exactly the ones you will see.
Concept#
The Parameter Manager is a Virtual Instrument: an instrument the Server owns that has no hardware behind it. Its parameters are ordinary QCoDeS parameters on the Server, which is what makes it the single source of truth:
Every Client and every GUI reads the same values, because there is only one copy, and it lives in the Server. A Proxy Instrument never keeps a diverging local value.
Every change is announced as a Broadcast, so GUIs and Listeners follow along without polling. The wire format is described in Broadcasts.
Two Clients see the same values with no refresh in between:
>>> pm_a = Client().find_or_create_instrument(
... "parameter_manager",
... "instrumentserver.params.ParameterManager",
... )
>>> pm_a.add_parameter("q01.IF", initial_value=10e6, unit="Hz")
>>> pm_b = Client().get_instrument("parameter_manager")
>>> pm_b.q01.IF()
10000000.0
>>> pm_a.q01.IF.set(11e6)
>>> pm_b.q01.IF()
11000000.0
pm_b never refreshed anything: the read asked the Server, and the Server
holds the one value. When the change happened, the Server also put a
parameter-update Broadcast on its PUB socket, which is what the Parameter
Manager GUI listens to. When the Parameter Manager itself creates or edits
something, for example a Lock or a Type, it announces that with its own
Broadcast; the Technical Guide page above lists them all.
Hierarchical parameters#
Parameters live at dotted paths relative to the Parameter Manager, such as
q01.readout.IF. Every level of the path is a Parameter Group, a plain
container of parameters and further Parameter Groups, created on demand the
first time a path needs it. Only the root is the Parameter Manager itself;
the groups hold no files, no Types and no Locks.
>>> pm.add_parameter("q01.readout.IF", initial_value=20e6, unit="Hz")
>>> pm.add_parameter("q01.power", initial_value=-10, unit="dBm")
>>> pm.list()
['q01.readout.IF', 'q01.power']
>>> pm.has_param("q01.readout.IF")
True
>>> pm.has_param("q01.readout.bw")
False
Through the Proxy Instrument, the hierarchy is attribute access, and parameters read and set like any QCoDeS parameter:
>>> pm.q01.readout.IF()
20000000.0
>>> pm.q01.readout.IF.set(21e6)
>>> pm.q01.readout.IF()
21000000.0
>>> pm.q01.readout.IF.unit
'Hz'
Note
The Proxy reflects the Server-side tree as it was when the Proxy was built or
last refreshed. When someone else creates parameters (another Client, or a
method that creates parameters as a side effect such as add_instance), call
pm.update() to pick them up.
remove_parameter deletes a parameter and prunes the Parameter Groups it
empties:
>>> pm.remove_parameter("q01.readout.IF")
>>> pm.list()
['q01.power']
To keep emptied Parameter Groups around instead, pass cleanup=False to
remove_parameter and sweep them up later with remove_empty_submodules().
Types#
Lab devices repeat: six qubits on one chip, four delivery channels per module, each copy carrying the same parameters. The Parameter Manager makes the repetition explicit with a Type. A Type is a named shape: a set of relative parameter paths, each with a default value and a unit, plus Nested Types required at named submodules.
A Parameter Group that carries the whole shape is an Instance of that Type. Nothing stores that membership anywhere: an Instance is found by shape, on every query, and never registered. So a Parameter Group becomes an Instance the moment it carries the shape, and stops being one the moment it does not.
>>> pm.add_type("qubit")
>>> pm.add_type_parameter("qubit", "IF", default=10e6, unit="Hz")
>>> pm.add_type_parameter("qubit", "octave_gain", default=10, unit="dB")
>>> pm.instances_of("qubit")
[]
>>> pm.add_instance("qubit", "q01")
>>> pm.add_instance("qubit", "q02")
>>> pm.instances_of("qubit")
['q01', 'q02']
>>> pm.update()
>>> pm.q01.IF()
10000000.0
>>> pm.q01.octave_gain()
10
A fresh Type has no Instances, because nothing carries its shape yet.
add_instance writes the shape into a Parameter Group: every missing entry
is created with the Type’s default value and unit, and parameters that exist
at those paths already are kept as they are.
The shape-finding works in the other direction too. q03 below was never
registered anywhere; it simply carries the shape, so it counts:
>>> pm.add_parameter("q03.IF", initial_value=99e6, unit="Hz")
>>> pm.add_parameter("q03.octave_gain", initial_value=1, unit="dB")
>>> pm.instances_of("qubit")
['q01', 'q02', 'q03']
>>> pm.types_of("q03.IF")
['qubit']
The same cuts both ways: delete a parameter an Instance needs, and that
submodule stops matching, with no other change. q02 above stops being an
Instance the moment q02.IF is removed.
Editing a Type#
Adding an entry to a Type writes it into every Instance that lacks it, with the entry’s default and unit:
>>> pm.add_type_parameter("qubit", "window", default=0.5, unit="s")
>>> pm.update()
>>> pm.q03.window()
0.5
Changing the default touches nobody who exists; only parameters created later start with it:
>>> pm.set_type_parameter_default("qubit", "window", 1.0)
>>> pm.q01.window()
0.5
>>> pm.add_instance("qubit", "q04")
>>> pm.update()
>>> pm.q04.window()
1.0
set_type_parameter_unit changes the entry’s unit and propagates it to that
parameter in every Instance. The unit is part of the shape, so a parameter
with the wrong unit keeps its Parameter Group out of the Instance list.
Nested Types#
A Type can require another Type at one of its submodules. A qubit needs a
readout; the Nested Type expands under the submodule it is required at:
>>> pm.add_type("readout")
>>> pm.add_type_parameter("readout", "bw", default=20e6, unit="Hz")
>>> pm.add_nested_type("qubit", "readout", "readout")
>>> pm.update()
>>> pm.q01.readout.bw()
20000000.0
What a Type requires in total is its effective set: its own entries plus
every Nested Type’s entries under their submodule names. get_type returns
it together with the rest of the definition:
>>> qubit = pm.get_type("qubit")
>>> qubit.nested
{'readout': 'readout'}
>>> qubit.effective["readout.bw"]
{'unit': 'Hz', 'from_type': 'readout'}
When several Types cover one parameter, the innermost Instance wins, then the one with the larger effective set. That Claiming Type is what the GUI tints a row with:
>>> pm.types_of("q01.readout.bw")
['readout', 'qubit']
>>> pm.instances_of("readout")
['q01.readout', 'q02.readout', 'q03.readout', 'q04.readout']
Removing from a Type#
Removing an entry, a Nested Type or a whole Type leaves every parameter alone. The submodules keep what they carry; matching simply follows the shape, which the removal changed:
>>> pm.remove_type_parameter("qubit", "window")
>>> pm.has_param("q01.window")
True
>>> pm.remove_nested_type("qubit", "readout")
>>> pm.has_param("q01.readout.bw")
True
>>> pm.remove_type("qubit")
>>> pm.list_types()
['readout']
>>> pm.has_param("q01.IF")
True
A Type that is still required as a Nested Type refuses to be removed, naming the Types that nest it.
Locks#
Shared settings want one authoritative value. A Lock ties a parameter (the Follower) to another parameter (its Target). A Lock has three states:
no Lock: the parameter behaves as a plain parameter;
a Lock that is present but unlocked: the parameter answers
getwith its own value, but remembers its Target so it can be locked again;a Lock that is locked: the parameter answers
getwith the Target’s value and refusesset.
>>> pm.add_parameter("q01Data.IF", initial_value=10e6, unit="Hz")
>>> pm.add_parameter("q01.IF", initial_value=5e6, unit="Hz")
>>> pm.lock("q01.IF", "q01Data.IF")
>>> pm.get_lock("q01.IF")
PMLockBluePrint(target='parameter_manager.q01Data.IF', locked=True, _class_type='PMLockBluePrint')
While locked, the Follower pulls the Target’s value on every get. Nothing
is ever pushed into the Follower, and the Target knows nothing about its
Followers:
>>> pm.q01.IF()
10000000.0
>>> pm.q01Data.IF.set(11e6)
>>> pm.q01.IF()
11000000.0
A locked Follower refuses set with an error naming the Target. Over the
connection, Server-side errors arrive as a generic Exception (the
Python Client page describes this), and the message carries the
pair:
>>> try:
... pm.q01.IF.set(12e6)
... except Exception as exc:
... message = str(exc)
>>> "parameter_manager.q01.IF is locked to parameter_manager.q01Data.IF" in message
True
Locking never touches the Follower’s own value. Unlocking exposes it again,
and relocking goes back to the Target; toggle_lock switches between the
two states:
>>> pm.unlock("q01.IF")
>>> pm.q01.IF()
5000000.0
>>> pm.relock("q01.IF")
>>> pm.q01.IF()
11000000.0
>>> pm.toggle_lock("q01.IF")
>>> pm.get_lock("q01.IF").locked
False
>>> pm.toggle_lock("q01.IF")
>>> pm.get_lock("q01.IF").locked
True
remove_lock removes a Lock entirely and forgets the Target.
list_locks reports every Lock in the Parameter Manager, and
followers_of names the Followers of one parameter:
>>> pm.followers_of("q01Data.IF")
['q01.IF']
>>> pm.list_locks()
{'q01.IF': PMLockBluePrint(target='parameter_manager.q01Data.IF', locked=True, _class_type='PMLockBluePrint')}
A Lock never points at its own parameter, and its Target lives in the same Parameter Manager.
Chains and cycles#
A Target may itself have a Lock, and each hop reads according to its own state at read time:
>>> pm.add_parameter("q02.IF", initial_value=0, unit="Hz")
>>> pm.lock("q02.IF", "q01.IF")
>>> pm.q02.IF()
11000000.0
>>> pm.unlock("q01.IF")
>>> pm.q02.IF()
5000000.0
q02.IF is still locked, but it reads through q01.IF, which now answers
with its own value. Relock q01.IF, and q02.IF follows the Target again.
Locks may chain but never cycle. A Lock that would close a cycle is refused, with the whole chain in the error:
>>> try:
... pm.lock("q01Data.IF", "q02.IF")
... except Exception as exc:
... cycle_message = str(exc)
>>> cycle_message
'cannot lock parameter_manager.q01Data.IF to parameter_manager.q02.IF: cycle in Lock targets: parameter_manager.q02.IF -> parameter_manager.q01.IF -> parameter_manager.q01Data.IF'
Deleting a parameter removes the Locks that pointed at it; its Followers
become plain parameters and answer get with their own values again. A
Follower of a survivor keeps its Lock:
>>> pm.remove_parameter("q01Data.IF")
>>> pm.get_lock("q01.IF")
None
>>> pm.q01.IF()
5000000.0
>>> pm.get_lock("q02.IF")
PMLockBluePrint(target='parameter_manager.q01.IF', locked=True, _class_type='PMLockBluePrint')
>>> pm.remove_lock("q02.IF")
>>> pm.list_locks()
{}
Every Lock method that changes a Lock emits one pm-lock-update Broadcast
per affected Follower, which is how the GUI and any other listener stay
current; Broadcasts shows the payload.
Type Locks and Globals#
One Lock ties two parameters together. Usually you want one value shared by every Instance of a Type: one LO frequency for all qubits. A Type Lock declares that on a Type entry, and every Instance follows:
>>> pm.add_type("qubit")
>>> pm.add_type_parameter("qubit", "IF", default=10e6, unit="Hz")
>>> pm.add_instance("qubit", "q01")
>>> pm.add_instance("qubit", "q02")
>>> pm.update()
>>> pm.lock_type_parameter("qubit", "IF")
[]
With no Target given, the Target is the Globals parameter
_globals.<type>.<path>, created on demand with the entry’s default value
and unit. Every current Instance gets an ordinary, locked Lock on it:
>>> pm.has_param("_globals.qubit.IF")
True
>>> cli.call("parameter_manager.get", "_globals.qubit.IF")
10000000.0
>>> pm.get_lock("q01.IF")
PMLockBluePrint(target='parameter_manager._globals.qubit.IF', locked=True, _class_type='PMLockBluePrint')
Setting the Globals parameter moves every Follower at once:
>>> cli.call("parameter_manager.set", "_globals.qubit.IF", 12e6)
>>> pm.q01.IF()
12000000.0
>>> pm.q02.IF()
12000000.0
Note
Why Client.call? On a Proxy Instrument, pm.get and pm.set are QCoDeS’
own local shorthands, and they only take a plain parameter name: a dotted
path raises KeyError. Attribute access does reach the Globals submodule
once the Proxy knows it; a Proxy built before the Globals parameter existed
needs one pm.update() first. The Parameter Manager’s own dotted get and
set run through Client.call, as above.
An explicit Target names any parameter of the same Parameter Manager instead:
>>> pm.add_parameter("lo.frequency", initial_value=1e6, unit="Hz")
>>> pm.remove_lock("q02.IF") # let q02 join the new Target below
>>> pm.lock_type_parameter("qubit", "IF", target="lo.frequency")
['q01.IF']
The return value names the Instance parameters that were skipped, and the
Parameter Manager logs a warning: q01.IF already carried a Lock on another
Target (the Globals parameter), and a Lock is never re-pointed behind its
holder’s back. Everything else is locked to the new Target. Declaring the
same Type Lock again is how the GUI’s lock all button re-applies it to
everyone.
unlock_type_parameter removes only the rule. The Locks it created stay
until they are removed individually, and Instances created after the removal
get no Lock:
>>> pm.unlock_type_parameter("qubit", "IF")
>>> pm.get_type("qubit").parameters["IF"]["target"] is None
True
>>> pm.get_lock("q02.IF") is not None
True
>>> pm.add_instance("qubit", "q03")
>>> pm.get_lock("q03.IF")
None
Globals itself is never an Instance of anything, and its parameters are
created on demand by the Type Lock, not through add_parameter, which
refuses the name with “the Globals submodule name is reserved”. Otherwise a
Globals parameter is ordinary: it shows up in list(), can be read and set,
and is saved with the profile.
Profiles and files#
A Parameter Manager saves itself as a JSON profile document in the working directory of the Server process, so the state survives restarts and can be switched per experiment. This section starts from two parameters and a Lock between them, so the document below shows all three shapes:
>>> pm.add_parameter("lo.frequency", initial_value=5e9, unit="Hz")
>>> pm.add_parameter("q01.IF", initial_value=10e6, unit="Hz")
>>> pm.lock("q01.IF", "lo.frequency")
>>> pm.toFile()
The default file is parameter_manager-parameter_manager.json, and it holds
the version-2 document:
{
"parameters": {
"parameter_manager.lo.frequency": {
"unit": "Hz",
"value": 5000000000.0
},
"parameter_manager.q01.IF": {
"lock": {
"locked": true,
"target": "parameter_manager.lo.frequency"
},
"unit": "Hz",
"value": 10000000.0
}
},
"types": {},
"version": 2
}
Three things to read off that document:
Parameter keys are the full dotted paths with the instrument name in front, the same form the Locks and the Type Lock Targets use.
valueis always the parameter’s own value. A locked Follower saves its own value plus itslock, never the Target’s value, so unlocking exposes what was saved.lockappears only on parameters that carry a Lock, in either state. Thetypessection holds the Type definitions, with each entry’s default, unit and Type Lock Target.
fromFile loads a document back, restoring own values and Lock states:
>>> pm.lo.frequency.set(6e9)
>>> pm.fromFile()
>>> pm.lo.frequency()
5000000000.0
>>> pm.get_lock("q01.IF")
PMLockBluePrint(target='parameter_manager.lo.frequency', locked=True, _class_type='PMLockBluePrint')
Loading removes parameters the document does not list; that is the
deleteMissing default of the reader underneath, and fromFile always
loads with that default today. The dictionary variant takes it explicitly,
so a document that omits one parameter shows the difference:
>>> pm.add_parameter("temp.extra", initial_value=1)
>>> document = pm.toParamDict()
>>> del document["parameters"]["parameter_manager.temp.extra"]
>>> pm.fromParamDict(document, deleteMissing=False)
>>> pm.has_param("temp.extra")
True
>>> pm.fromParamDict(document)
>>> pm.has_param("temp.extra")
False
Any file named parameter_manager-<profile>.json in the working directory
is a profile. refresh_profiles re-reads the directory, list_profiles
reports what it found, and switch_to_profile moves between them: it saves
the profile being left, clears every parameter, Type and Lock, then loads
the profile you name:
>>> pm.toFile(name="cooldown")
>>> sorted(pm.refresh_profiles())
['parameter_manager-cooldown.json', 'parameter_manager-parameter_manager.json']
>>> pm.unlock("q01.IF")
>>> pm.lo.frequency.set(8e9)
>>> pm.switch_to_profile("parameter_manager")
>>> pm.lo.frequency()
5000000000.0
>>> pm.get_lock("q01.IF").locked
True
>>> pm.switch_to_profile("cooldown")
>>> pm.lo.frequency()
8000000000.0
>>> pm.get_lock("q01.IF")
PMLockBluePrint(target='parameter_manager.lo.frequency', locked=False, _class_type='PMLockBluePrint')
A file without a top-level version key is the old flat map from before
Types and Locks existed. Write one (any file named
parameter_manager-<profile>.json works) and load it:
{
"parameter_manager.old_target": {"unit": "Hz", "value": 5},
"parameter_manager.old_param": {"unit": "M", "value": 123}
}
>>> pm.add_parameter("old_target", initial_value=5, unit="Hz")
>>> pm.add_parameter("old_param", initial_value=1, unit="M")
>>> pm.lock("old_param", "old_target")
>>> pm.unlock("old_param") # present but unlocked, so the load can set its value
>>> pm.fromFile("parameter_manager-legacy.json")
>>> pm.old_param()
123
>>> pm.list_locks()
{'old_param': PMLockBluePrint(target='parameter_manager.old_target', locked=False, _class_type='PMLockBluePrint')}
Note
The legacy reader writes no Types and no Locks. Parameters it does not list
are removed as above, their Locks with them; a Lock whose parameters stay is
untouched, exactly as the load above leaves the unlocked old_param Lock.
Saving always writes the version-2 document, so one save over an old file
upgrades it.
The GUI#
The Parameter Manager GUI is one window with two tabs, Parameters and Types. You can run it standalone:
instrumentserver-param-manager --port 5555
The launcher connects a Client to the Server on that port, creates the
Parameter Manager named parameter_manager if it does not exist yet, and
opens the window. --name chooses a different Parameter Manager. The Server
window itself opens the generic instrument widget for a Parameter Manager
unless the station config’s gui entry names
instrumentserver.gui.parameter_manager.ParameterManagerGui, as the
serverConfig.yml in the repository does; then the Server window embeds the
same widget, and the launcher above is the sure way to get it. Configs that
still name the older path instrumentserver.gui.instruments.ParameterManagerGui
keep working.
the Server covers launching and the station config, and
GUI features describes the gui entry and the patterns
shared by every instrument window: starring, trashing, filtering, and the
detachable tabs.
The Parameters tab#
The Parameters tab is the tree of parameters: name, unit, a value editor per row, and the strip at the bottom to add a parameter. Three things are specific to the Parameter Manager:
Tints and gutter bands. Every parameter of an Instance is tinted with the colour of its Claiming Type, and the thin coloured band at the left edge stacks one segment per Type covering the row, outermost first, up to three. When a Client edits a Type, the tints follow live, because the Parameter Manager emits a
pm-type-updateBroadcast.
📸 SCREENSHOT NEEDED
The Parameters tab with two Types tinting their rows: the parameter tree
with qubit rows in one tint and readout rows in another, the coloured
gutter bands stacked at the left edge, the “locked to” column showing
locked to _globals.qubit.IF on the locked rows, and the lock button on one
of them. Light theme:
docs/_static/user_guide/parameter_manager/tree_tints_light.png, dark
theme: docs/_static/user_guide/parameter_manager/tree_tints_dark.png.
The “locked to” column. A Follower shows
locked to <target>while its Lock is locked andunlocked · <target>while unlocked; a parameter that is a Target showstarget ×Nfor its N Followers. The lock button on the row, purple while locked, toggles the Lock.The context menu. Right-clicking a row offers “Lock to…” and “Unlock” beside the usual actions.
“Lock to…” arms the Target picker: a strip appears under the toolbar, naming the Follower, with a line edit that completes over every parameter path, ranked so that the same relative path on another Instance comes first, then paths containing that relative path, then the rest, and a Cancel button. Click a tree row or complete a path to pick the Target. If the Server refuses, for a cycle for example, the strip shows the error.
📸 SCREENSHOT NEEDED
The arm strip in action: the strip under the toolbar reading
“Target for q01.IF”, the line edit showing a typed partial path with the
completion popup open over the candidate paths, and the Cancel button at
its right. Light theme:
docs/_static/user_guide/parameter_manager/arm_strip_light.png, dark
theme: docs/_static/user_guide/parameter_manager/arm_strip_dark.png.
The Locks panel#
The lock icon in the toolbar, or Ctrl+Shift+L, opens the Locks panel next to
the tree. It shows the same Locks from the Target side: one row per Target,
with each Target’s Followers nested beneath it, recursively for chains. Type
Lock Targets come first and are labelled with their Type, as
[type: qubit] _globals.qubit.IF.
Each row carries what it needs. A Target row has a value editor, and setting it repaints every Follower watching. A locked Follower row shows its value read-only, with a lock or relock toggle and a remove button. A Type Lock row has lock all, which re-applies the Type Lock to every Instance, and remove rule, which removes only the rule and leaves the Locks. “Lock selection to…” arms the picker for the row currently selected in the tree.
📸 SCREENSHOT NEEDED
The Locks panel open next to the tree: the Type Lock Target row labelled
[type: qubit] _globals.qubit.IF with its value editor, two Follower rows
nested beneath it with read-only values and their toggle and remove buttons,
and the “Lock selection to…” button with the selected-parameter label at the
bottom. Light theme:
docs/_static/user_guide/parameter_manager/locks_panel_light.png, dark
theme: docs/_static/user_guide/parameter_manager/locks_panel_dark.png.
The Types tab#
The Types tab (Ctrl+Shift+Y switches between the two tabs) keeps the structure work in one place, in three panes:
Left: the list of Types, each with its number of Instances and parameters, tinted in the Type’s colour, and the “New type:” strip below.
Top right: the selected Type’s entries as a tree. An entry of the Type itself has an editable default, a Remove button, and the Type Lock toggle in the “locked to” column; while the entry is locked, the column also shows a re-target button and the Target’s path. Entries that come from a Nested Type are read-only and say “defined by
”; submodule rows show which Type they require and can drop the requirement. The “Add to type” and “Nested type” strips below add entries and Nested Type requirements. Bottom right: the Instances of the selected Type, each with its parameter count and the other Types it also carries. “Show” jumps to the Parameters tab and selects the Instance; the “New instance:” strip adds one.
📸 SCREENSHOT NEEDED
The Types tab with a Type selected: the tinted type list on the left, the
entries tree in the top right pane with an editable default, a Type Lock
toggle and a “defined by readout” row, the two strips below it, and the
Instances pane in the bottom right with one Instance row and its Show
button. Light theme:
docs/_static/user_guide/parameter_manager/types_tab_light.png, dark
theme: docs/_static/user_guide/parameter_manager/types_tab_dark.png.
Deleting a Target#
Deleting a parameter that others follow is the one destructive action with a safety net: the Parameter Manager names every Follower whose Lock will vanish and asks for confirmation. Cancel leaves the Server untouched; a parameter without Followers is deleted without asking.
📸 SCREENSHOT NEEDED
The “Remove Target?” confirmation dialog for a parameter with two
Followers: the question text listing each Follower with its Lock state,
with Cancel as the default button. Light theme:
docs/_static/user_guide/parameter_manager/delete_confirmation_light.png,
dark theme:
docs/_static/user_guide/parameter_manager/delete_confirmation_dark.png.
Keyboard shortcuts#
The shortcuts below matter most in this window; the defaults come from the GUI’s shortcut registry, and GUI features shows how to rebind them.
Action |
Default keys |
What it does |
|---|---|---|
Delete parameter |
|
Delete the selected parameter |
Lock to… |
|
Lock the selected parameter to… (pick a Target) |
Unlock |
|
Unlock the selected parameter |
Show the Locks panel |
|
Show or hide the Locks panel |
Switch tabs |
|
Switch between the Parameters and Types tabs |
Add parameter |
|
Jump cursor to the add parameter bar |
Load parameters |
|
Load parameters from JSON file |
Save parameters |
|
Save parameters to JSON file |
Refresh all |
|
Refresh all parameters from instrument |
Using it from measurement code#
A measurement script treats the Parameter Manager like any instrument: find it or create it, then read and set through the Proxy Instrument. One-time setup declares the shape, and a Type Lock shares one value across every Instance:
>>> from instrumentserver.client import Client
>>> cli = Client()
>>> pm = cli.find_or_create_instrument(
... "parameter_manager",
... "instrumentserver.params.ParameterManager",
... )
>>> pm.add_parameter("power", initial_value=-10, unit="dBm")
>>> pm.add_type("qubit")
>>> pm.add_type_parameter("qubit", "IF", default=10e6, unit="Hz")
>>> pm.add_instance("qubit", "q0")
>>> pm.add_instance("qubit", "q1")
>>> pm.update()
>>> pm.lock_type_parameter("qubit", "IF")
[]
The sweep sets the Globals Target and reads the Followers; both qubits move together, and any GUI watching follows live:
>>> for if_hz in (10e6, 11e6, 12e6):
... cli.call("parameter_manager.set", "_globals.qubit.IF", if_hz)
... print(pm.q0.IF(), pm.q1.IF())
10000000.0 10000000.0
11000000.0 11000000.0
12000000.0 12000000.0
When one qubit has to deviate, unlock it first: setting a locked Follower
raises, naming its Target. The Follower keeps its own value the whole time,
so unlocking exposes it, and relock rejoins the Target:
>>> try:
... pm.q0.IF.set(13e6)
... except Exception as exc:
... "is locked to" in str(exc)
True
>>> pm.unlock("q0.IF")
>>> pm.q0.IF.set(13e6)
>>> pm.q0.IF()
13000000.0
>>> pm.q1.IF()
12000000.0
>>> pm.relock("q0.IF")
>>> pm.q0.IF()
12000000.0
At the end of the experiment, save the profile, and the next run starts where this one ended:
>>> pm.toFile()
The Python Client page covers the Client’s connection lifecycle and error handling. The profile files end up in the working directory of the Server process, as Profiles and files above describes, so where they live follows from where the Server runs; the Server page documents the Server itself.