Parameter handling

The tool within \(C^3\) to manipulate the parameters of both the model and controls is the ParameterMap. It provides methods to present the same data for human interaction, i.e. structured information with physical units and for numerical optimization algorithms that prefer a linear vector of scale 1. Here, we’ll show some example usage. We’ll use the ParameterMap of the model also used in the simulated calibration example.

from single_qubit_blackbox_exp import create_experiment

exp = create_experiment()
pmap = exp.pmap

The pmap contains a list of all parameters and their values:

pmap.get_full_params()
{'Q1-freq': 5.000 GHz 2pi,
 'Q1-anhar': -210.000 MHz 2pi,
 'Q1-temp': 0.000 K,
 'init_ground-init_temp': -3.469 aK,
 'resp-rise_time': 300.000 ps,
 'v_to_hz-V_to_Hz': 1.000 GHz/V,
 'id[0]-d1-no_drive-amp': 1.000 V,
 'id[0]-d1-no_drive-delta': 0.000 V,
 'id[0]-d1-no_drive-freq_offset': 0.000 Hz 2pi,
 'id[0]-d1-no_drive-xy_angle': 0.000 rad,
 'id[0]-d1-no_drive-sigma': 5.000 ns,
 'id[0]-d1-no_drive-t_final': 7.000 ns,
 'id[0]-d1-carrier-freq': 5.050 GHz 2pi,
 'id[0]-d1-carrier-framechange': 5.933 rad,
 'rx90p[0]-d1-gauss-amp': 450.000 mV,
 'rx90p[0]-d1-gauss-delta': -1.000 ,
 'rx90p[0]-d1-gauss-freq_offset': -50.500 MHz 2pi,
 'rx90p[0]-d1-gauss-xy_angle': -444.089 arad,
 'rx90p[0]-d1-gauss-sigma': 1.750 ns,
 'rx90p[0]-d1-gauss-t_final': 7.000 ns,
 'rx90p[0]-d1-carrier-freq': 5.050 GHz 2pi,
 'rx90p[0]-d1-carrier-framechange': 0.000 rad,
 'ry90p[0]-d1-gauss-amp': 450.000 mV,
 'ry90p[0]-d1-gauss-delta': -1.000 ,
 'ry90p[0]-d1-gauss-freq_offset': -50.500 MHz 2pi,
 'ry90p[0]-d1-gauss-xy_angle': 1.571 rad,
 'ry90p[0]-d1-gauss-sigma': 1.750 ns,
 'ry90p[0]-d1-gauss-t_final': 7.000 ns,
 'ry90p[0]-d1-carrier-freq': 5.050 GHz 2pi,
 'ry90p[0]-d1-carrier-framechange': 0.000 rad,
 'rx90m[0]-d1-gauss-amp': 450.000 mV,
 'rx90m[0]-d1-gauss-delta': -1.000 ,
 'rx90m[0]-d1-gauss-freq_offset': -50.500 MHz 2pi,
 'rx90m[0]-d1-gauss-xy_angle': 3.142 rad,
 'rx90m[0]-d1-gauss-sigma': 1.750 ns,
 'rx90m[0]-d1-gauss-t_final': 7.000 ns,
 'rx90m[0]-d1-carrier-freq': 5.050 GHz 2pi,
 'rx90m[0]-d1-carrier-framechange': 0.000 rad,
 'ry90m[0]-d1-gauss-amp': 450.000 mV,
 'ry90m[0]-d1-gauss-delta': -1.000 ,
 'ry90m[0]-d1-gauss-freq_offset': -50.500 MHz 2pi,
 'ry90m[0]-d1-gauss-xy_angle': 4.712 rad,
 'ry90m[0]-d1-gauss-sigma': 1.750 ns,
 'ry90m[0]-d1-gauss-t_final': 7.000 ns,
 'ry90m[0]-d1-carrier-freq': 5.050 GHz 2pi,
 'ry90m[0]-d1-carrier-framechange': 0.000 rad}

To access a specific parameter, e.g. the frequency of qubit 1, we use the identifying tuple ('Q1','freq').

pmap.get_parameter(('Q1','freq'))
5.000 GHz 2pi

The opt_map

To deal with multiple parameters we use the opt_map, a nested list of identifyers.

opt_map = [
    [
        ("Q1", "freq")
    ],
    [
        ("Q1", "anhar")
    ],
]

Here, we get a list of the parameter values:

pmap.get_parameters(opt_map)
[5.000 GHz 2pi, -210.000 MHz 2pi]

Let’s look at the amplitude values of two gaussian control pulses, rotations about the \(X\) and \(Y\) axes repsectively.

opt_map = [
    [
        ('rx90p[0]','d1','gauss','amp')
    ],
    [
        ('ry90p[0]','d1','gauss','amp')
    ],
]
pmap.get_parameters(opt_map)
[450.000 mV, 450.000 mV]

We can set the parameters to new values.

pmap.set_parameters([0.5, 0.6], opt_map)
pmap.get_parameters(opt_map)
[500.000 mV, 600.000 mV]

The opt_map also allows us to specify that two parameters should have identical values. Here, let’s demand our \(X\) and \(Y\) rotations use the same amplitude.

opt_map_ident = [
    [
        ('rx90p[0]','d1','gauss','amp'),
        ('ry90p[0]','d1','gauss','amp')
    ],
]

The grouping here means that these parameters share their numerical value.

pmap.set_parameters([0.432], opt_map_ident)
pmap.get_parameters(opt_map_ident)
[432.000 mV]
pmap.get_parameters(opt_map)
[432.000 mV, 432.000 mV]

During an optimization, the varied parameters do not change, so we fix the opt_map

pmap.set_opt_map(opt_map)
pmap.get_parameters()
[432.000 mV, 432.000 mV]

Optimizer scaling

To be independent of the choice of numerical optimizer, they should use the methods

pmap.get_parameters_scaled()
array([-0.68, -0.68])

To provide values bound to \([-1, 1]\). Let’s set the parameters to their allowed minimum an maximum value with

pmap.set_parameters_scaled([1.0,-1.0])
pmap.get_parameters()
[600.000 mV, 400.000 mV]

As a safeguard, when setting values outside of the unit range, their physical values get looped back in the specified limits.

pmap.set_parameters_scaled([2.0, 3.0])
pmap.get_parameters()
[500.000 mV, 400.000 mV]

Storing and reading

For optimization purposes, we can store and load parameter values in HJSON format.

pmap.store_values("current_vals.c3log")
!cat current_vals.c3log
{
  opt_map:
  [
    [
      rx90p[0]-d1-gauss-amp
    ]
    [
      ry90p[0]-d1-gauss-amp
    ]
  ]
  units:
  [
    V
    V
  ]
  optim_status:
  {
    params:
    [
      0.5
      0.4000000059604645
    ]
  }
}
pmap.load_values("current_vals.c3log")