diff --git a/cirq-google/cirq_google/api/v2/run_context.proto b/cirq-google/cirq_google/api/v2/run_context.proto index e3722cb652a..1e7fc630bd0 100644 --- a/cirq-google/cirq_google/api/v2/run_context.proto +++ b/cirq-google/cirq_google/api/v2/run_context.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +import "cirq_google/api/v2/program.proto"; + package cirq.google.api.v2; option java_package = "com.google.cirq.google.api.v2"; @@ -10,6 +12,13 @@ option java_multiple_files = true; message RunContext { // The parameters for operations in a program. repeated ParameterSweep parameter_sweeps = 1; + + // Optional override of select device parameters before program + // execution. Note it is permissible to specify the same device parameter + // here and in a parameter_sweeps, as sweep.single_sweep.parameter. + // If the same parameter is supplied in both places, the provision here in + // device_parameters_override will have no effect. + DeviceParametersDiff device_parameters_override = 2; } // Specifies how to repeatedly sample a circuit, with or without sweeping over @@ -100,6 +109,41 @@ message DeviceParameter { // by the sweep values themselves. } +// A bundle of multiple DeviceParameters and their values. +// The main use case is to set those parameters with the +// values from this bundle before executing a circuit sweep. +// Note multiple device parameters may have common ancestor paths +// and/or share the same parameter names. A DeviceParametersDiff +// stores the resource groups hierarchy extracted from the DeviceParameters' +// paths and maintains a table of strings; thereby storing ancestor resource +// groups only once, and avoiding repeated storage of common parameter names. +message DeviceParametersDiff { + // A resource group a device parameter belongs to. + // The identifier of a resource group is DeviceParameter.path without the + // last component. + message ResourceGroup { + // parent resource group, as an index into the groups repeated field. + int32 parent = 1; + // as index into the strs repeated field. + int32 name = 2; + } + message Param { + // the resource group hosting this parameter key, as index into groups + // repeated field. + int32 resource_group = 1; + // name of this param, as index into the strs repeated field. + int32 name = 2; + // this param's new value, as message ArgValue to allow variant types, + // including bool, string, double, float and arrays. + ArgValue value = 3; + } + repeated ResourceGroup groups = 1; + repeated Param params = 2; + + // List of all key, dir, and deletion names in these contents. + // ResourceGroup.name, Param.name, and Deletion.name are indexes into this list. + repeated string strs = 4; +} // A set of values to loop over for a particular parameter. message SingleSweep { diff --git a/cirq-google/cirq_google/api/v2/run_context.py b/cirq-google/cirq_google/api/v2/run_context.py new file mode 100644 index 00000000000..1e458699569 --- /dev/null +++ b/cirq-google/cirq_google/api/v2/run_context.py @@ -0,0 +1,69 @@ +# Copyright 2024 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools +from typing import Sequence +from cirq_google.api.v2 import program_pb2 +from cirq_google.api.v2 import run_context_pb2 + + +# The special index of an empty directory path []. +_EMPTY_RESOURCE_PATH_IDX = -1 + + +def to_device_parameters_diff( + device_params: Sequence[tuple[run_context_pb2.DeviceParameter, program_pb2.ArgValue]] +) -> run_context_pb2.DeviceParametersDiff: + """Constructs a DeviceParametersDiff from multiple DeviceParameters and values. + + Args: + device_params: a list of (DeviceParameter, value) pairs. + + Returns: + a DeviceParametersDiff, which comprises the entire device_params. + """ + diff = run_context_pb2.DeviceParametersDiff() + + @functools.lru_cache(maxsize=None) + def token_id(s: str) -> int: + """Computes the index of s in the string table diff.strs.""" + idx = len(diff.strs) + diff.strs.append(s) + return idx + + # Maps a resource group path to its index in diff.groups. + resource_groups_index: dict[tuple[str, ...], int] = {tuple(): _EMPTY_RESOURCE_PATH_IDX} + + def resource_path_id(path: tuple[str, ...]) -> int: + """Computes the index of a path in diff.groups.""" + idx = resource_groups_index.get(path, None) + if idx is not None: + return idx + # Recursive call to get the assigned index of the parent. Note the base case + # of the empty path, which returns _EMPTY_RESOURCE_PATH_IDX. + parent_id = resource_path_id(path[:-1]) + # This path has not been seen. It will be appended to diff.groups, with idx as + # the size of diff.groups before appending. + idx = len(diff.groups) + diff.groups.add(parent=parent_id, name=token_id(path[-1])) + resource_groups_index[path] = idx + return idx + + for device_param, value in device_params: + resource_path = tuple(device_param.path[:-1]) + param_name = device_param.path[-1] + path_id = resource_path_id(resource_path) + diff.params.add(name=token_id(param_name), resource_group=path_id, value=value) + + return diff diff --git a/cirq-google/cirq_google/api/v2/run_context_pb2.py b/cirq-google/cirq_google/api/v2/run_context_pb2.py index ca9991c2816..2bd0ab8d74d 100644 --- a/cirq-google/cirq_google/api/v2/run_context_pb2.py +++ b/cirq-google/cirq_google/api/v2/run_context_pb2.py @@ -11,32 +11,40 @@ _sym_db = _symbol_database.Default() +from . import program_pb2 as cirq__google_dot_api_dot_v2_dot_program__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$cirq_google/api/v2/run_context.proto\x12\x12\x63irq.google.api.v2\"J\n\nRunContext\x12<\n\x10parameter_sweeps\x18\x01 \x03(\x0b\x32\".cirq.google.api.v2.ParameterSweep\"O\n\x0eParameterSweep\x12\x13\n\x0brepetitions\x18\x01 \x01(\x05\x12(\n\x05sweep\x18\x02 \x01(\x0b\x32\x19.cirq.google.api.v2.Sweep\"\x86\x01\n\x05Sweep\x12;\n\x0esweep_function\x18\x01 \x01(\x0b\x32!.cirq.google.api.v2.SweepFunctionH\x00\x12\x37\n\x0csingle_sweep\x18\x02 \x01(\x0b\x32\x1f.cirq.google.api.v2.SingleSweepH\x00\x42\x07\n\x05sweep\"\xc6\x01\n\rSweepFunction\x12\x45\n\rfunction_type\x18\x01 \x01(\x0e\x32..cirq.google.api.v2.SweepFunction.FunctionType\x12)\n\x06sweeps\x18\x02 \x03(\x0b\x32\x19.cirq.google.api.v2.Sweep\"C\n\x0c\x46unctionType\x12\x1d\n\x19\x46UNCTION_TYPE_UNSPECIFIED\x10\x00\x12\x0b\n\x07PRODUCT\x10\x01\x12\x07\n\x03ZIP\x10\x02\"W\n\x0f\x44\x65viceParameter\x12\x0c\n\x04path\x18\x01 \x03(\t\x12\x10\n\x03idx\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\x12\n\x05units\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x06\n\x04_idxB\x08\n\x06_units\"\xc5\x01\n\x0bSingleSweep\x12\x15\n\rparameter_key\x18\x01 \x01(\t\x12,\n\x06points\x18\x02 \x01(\x0b\x32\x1a.cirq.google.api.v2.PointsH\x00\x12\x30\n\x08linspace\x18\x03 \x01(\x0b\x32\x1c.cirq.google.api.v2.LinspaceH\x00\x12\x36\n\tparameter\x18\x04 \x01(\x0b\x32#.cirq.google.api.v2.DeviceParameterB\x07\n\x05sweep\"\x18\n\x06Points\x12\x0e\n\x06points\x18\x01 \x03(\x02\"G\n\x08Linspace\x12\x13\n\x0b\x66irst_point\x18\x01 \x01(\x02\x12\x12\n\nlast_point\x18\x02 \x01(\x02\x12\x12\n\nnum_points\x18\x03 \x01(\x03\x42\x32\n\x1d\x63om.google.cirq.google.api.v2B\x0fRunContextProtoP\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$cirq_google/api/v2/run_context.proto\x12\x12\x63irq.google.api.v2\x1a cirq_google/api/v2/program.proto\"\x98\x01\n\nRunContext\x12<\n\x10parameter_sweeps\x18\x01 \x03(\x0b\x32\".cirq.google.api.v2.ParameterSweep\x12L\n\x1a\x64\x65vice_parameters_override\x18\x02 \x01(\x0b\x32(.cirq.google.api.v2.DeviceParametersDiff\"O\n\x0eParameterSweep\x12\x13\n\x0brepetitions\x18\x01 \x01(\x05\x12(\n\x05sweep\x18\x02 \x01(\x0b\x32\x19.cirq.google.api.v2.Sweep\"\x86\x01\n\x05Sweep\x12;\n\x0esweep_function\x18\x01 \x01(\x0b\x32!.cirq.google.api.v2.SweepFunctionH\x00\x12\x37\n\x0csingle_sweep\x18\x02 \x01(\x0b\x32\x1f.cirq.google.api.v2.SingleSweepH\x00\x42\x07\n\x05sweep\"\xc6\x01\n\rSweepFunction\x12\x45\n\rfunction_type\x18\x01 \x01(\x0e\x32..cirq.google.api.v2.SweepFunction.FunctionType\x12)\n\x06sweeps\x18\x02 \x03(\x0b\x32\x19.cirq.google.api.v2.Sweep\"C\n\x0c\x46unctionType\x12\x1d\n\x19\x46UNCTION_TYPE_UNSPECIFIED\x10\x00\x12\x0b\n\x07PRODUCT\x10\x01\x12\x07\n\x03ZIP\x10\x02\"W\n\x0f\x44\x65viceParameter\x12\x0c\n\x04path\x18\x01 \x03(\t\x12\x10\n\x03idx\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\x12\n\x05units\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x06\n\x04_idxB\x08\n\x06_units\"\xb7\x02\n\x14\x44\x65viceParametersDiff\x12\x46\n\x06groups\x18\x01 \x03(\x0b\x32\x36.cirq.google.api.v2.DeviceParametersDiff.ResourceGroup\x12>\n\x06params\x18\x02 \x03(\x0b\x32..cirq.google.api.v2.DeviceParametersDiff.Param\x12\x0c\n\x04strs\x18\x04 \x03(\t\x1a-\n\rResourceGroup\x12\x0e\n\x06parent\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\x05\x1aZ\n\x05Param\x12\x16\n\x0eresource_group\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\x05\x12+\n\x05value\x18\x03 \x01(\x0b\x32\x1c.cirq.google.api.v2.ArgValue\"\xc5\x01\n\x0bSingleSweep\x12\x15\n\rparameter_key\x18\x01 \x01(\t\x12,\n\x06points\x18\x02 \x01(\x0b\x32\x1a.cirq.google.api.v2.PointsH\x00\x12\x30\n\x08linspace\x18\x03 \x01(\x0b\x32\x1c.cirq.google.api.v2.LinspaceH\x00\x12\x36\n\tparameter\x18\x04 \x01(\x0b\x32#.cirq.google.api.v2.DeviceParameterB\x07\n\x05sweep\"\x18\n\x06Points\x12\x0e\n\x06points\x18\x01 \x03(\x02\"G\n\x08Linspace\x12\x13\n\x0b\x66irst_point\x18\x01 \x01(\x02\x12\x12\n\nlast_point\x18\x02 \x01(\x02\x12\x12\n\nnum_points\x18\x03 \x01(\x03\x42\x32\n\x1d\x63om.google.cirq.google.api.v2B\x0fRunContextProtoP\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cirq_google.api.v2.run_context_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\035com.google.cirq.google.api.v2B\017RunContextProtoP\001' - _globals['_RUNCONTEXT']._serialized_start=60 - _globals['_RUNCONTEXT']._serialized_end=134 - _globals['_PARAMETERSWEEP']._serialized_start=136 - _globals['_PARAMETERSWEEP']._serialized_end=215 - _globals['_SWEEP']._serialized_start=218 - _globals['_SWEEP']._serialized_end=352 - _globals['_SWEEPFUNCTION']._serialized_start=355 - _globals['_SWEEPFUNCTION']._serialized_end=553 - _globals['_SWEEPFUNCTION_FUNCTIONTYPE']._serialized_start=486 - _globals['_SWEEPFUNCTION_FUNCTIONTYPE']._serialized_end=553 - _globals['_DEVICEPARAMETER']._serialized_start=555 - _globals['_DEVICEPARAMETER']._serialized_end=642 - _globals['_SINGLESWEEP']._serialized_start=645 - _globals['_SINGLESWEEP']._serialized_end=842 - _globals['_POINTS']._serialized_start=844 - _globals['_POINTS']._serialized_end=868 - _globals['_LINSPACE']._serialized_start=870 - _globals['_LINSPACE']._serialized_end=941 + _globals['_RUNCONTEXT']._serialized_start=95 + _globals['_RUNCONTEXT']._serialized_end=247 + _globals['_PARAMETERSWEEP']._serialized_start=249 + _globals['_PARAMETERSWEEP']._serialized_end=328 + _globals['_SWEEP']._serialized_start=331 + _globals['_SWEEP']._serialized_end=465 + _globals['_SWEEPFUNCTION']._serialized_start=468 + _globals['_SWEEPFUNCTION']._serialized_end=666 + _globals['_SWEEPFUNCTION_FUNCTIONTYPE']._serialized_start=599 + _globals['_SWEEPFUNCTION_FUNCTIONTYPE']._serialized_end=666 + _globals['_DEVICEPARAMETER']._serialized_start=668 + _globals['_DEVICEPARAMETER']._serialized_end=755 + _globals['_DEVICEPARAMETERSDIFF']._serialized_start=758 + _globals['_DEVICEPARAMETERSDIFF']._serialized_end=1069 + _globals['_DEVICEPARAMETERSDIFF_RESOURCEGROUP']._serialized_start=932 + _globals['_DEVICEPARAMETERSDIFF_RESOURCEGROUP']._serialized_end=977 + _globals['_DEVICEPARAMETERSDIFF_PARAM']._serialized_start=979 + _globals['_DEVICEPARAMETERSDIFF_PARAM']._serialized_end=1069 + _globals['_SINGLESWEEP']._serialized_start=1072 + _globals['_SINGLESWEEP']._serialized_end=1269 + _globals['_POINTS']._serialized_start=1271 + _globals['_POINTS']._serialized_end=1295 + _globals['_LINSPACE']._serialized_start=1297 + _globals['_LINSPACE']._serialized_end=1368 # @@protoc_insertion_point(module_scope) diff --git a/cirq-google/cirq_google/api/v2/run_context_pb2.pyi b/cirq-google/cirq_google/api/v2/run_context_pb2.pyi index 75afaa133e1..9946ffecceb 100644 --- a/cirq-google/cirq_google/api/v2/run_context_pb2.pyi +++ b/cirq-google/cirq_google/api/v2/run_context_pb2.pyi @@ -3,6 +3,7 @@ isort:skip_file """ import builtins +import cirq_google.api.v2.program_pb2 import collections.abc import google.protobuf.descriptor import google.protobuf.internal.containers @@ -25,15 +26,26 @@ class RunContext(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor PARAMETER_SWEEPS_FIELD_NUMBER: builtins.int + DEVICE_PARAMETERS_OVERRIDE_FIELD_NUMBER: builtins.int @property def parameter_sweeps(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ParameterSweep]: """The parameters for operations in a program.""" + @property + def device_parameters_override(self) -> global___DeviceParametersDiff: + """Optional override of select device parameters before program + execution. Note it is permissible to specify the same device parameter + here and in a parameter_sweeps, as sweep.single_sweep.parameter. + If the same parameter is supplied in both places, the provision here in + device_parameters_override will have no effect. + """ def __init__( self, *, parameter_sweeps: collections.abc.Iterable[global___ParameterSweep] | None = ..., + device_parameters_override: global___DeviceParametersDiff | None = ..., ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["parameter_sweeps", b"parameter_sweeps"]) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["device_parameters_override", b"device_parameters_override"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["device_parameters_override", b"device_parameters_override", "parameter_sweeps", b"parameter_sweeps"]) -> None: ... global___RunContext = RunContext @@ -226,6 +238,94 @@ class DeviceParameter(google.protobuf.message.Message): global___DeviceParameter = DeviceParameter +@typing_extensions.final +class DeviceParametersDiff(google.protobuf.message.Message): + """A bundle of multiple DeviceParameters and their values. + The main use case is to set those parameters with the + values from this bundle before executing a circuit sweep. + Note multiple device parameters may have common ancestor paths + and/or share the same parameter names. A DeviceParametersDiff + stores the resource groups hierarchy extracted from the DeviceParameters' + paths and maintains a table of strings; thereby storing ancestor resource + groups only once, and avoiding repeated storage of common parameter names. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class ResourceGroup(google.protobuf.message.Message): + """A resource group a device parameter belongs to. + The identifier of a resource group is DeviceParameter.path without the + last component. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PARENT_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + parent: builtins.int + """parent resource group, as an index into the groups repeated field.""" + name: builtins.int + """as index into the strs repeated field.""" + def __init__( + self, + *, + parent: builtins.int = ..., + name: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name", "parent", b"parent"]) -> None: ... + + @typing_extensions.final + class Param(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RESOURCE_GROUP_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + resource_group: builtins.int + """the resource group hosting this parameter key, as index into groups + repeated field. + """ + name: builtins.int + """name of this param, as index into the strs repeated field.""" + @property + def value(self) -> cirq_google.api.v2.program_pb2.ArgValue: + """this param's new value, as message ArgValue to allow variant types, + including bool, string, double, float and arrays. + """ + def __init__( + self, + *, + resource_group: builtins.int = ..., + name: builtins.int = ..., + value: cirq_google.api.v2.program_pb2.ArgValue | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name", "resource_group", b"resource_group", "value", b"value"]) -> None: ... + + GROUPS_FIELD_NUMBER: builtins.int + PARAMS_FIELD_NUMBER: builtins.int + STRS_FIELD_NUMBER: builtins.int + @property + def groups(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___DeviceParametersDiff.ResourceGroup]: ... + @property + def params(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___DeviceParametersDiff.Param]: ... + @property + def strs(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """List of all key, dir, and deletion names in these contents. + ResourceGroup.name, Param.name, and Deletion.name are indexes into this list. + """ + def __init__( + self, + *, + groups: collections.abc.Iterable[global___DeviceParametersDiff.ResourceGroup] | None = ..., + params: collections.abc.Iterable[global___DeviceParametersDiff.Param] | None = ..., + strs: collections.abc.Iterable[builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["groups", b"groups", "params", b"params", "strs", b"strs"]) -> None: ... + +global___DeviceParametersDiff = DeviceParametersDiff + @typing_extensions.final class SingleSweep(google.protobuf.message.Message): """A set of values to loop over for a particular parameter.""" diff --git a/cirq-google/cirq_google/api/v2/run_context_test.py b/cirq-google/cirq_google/api/v2/run_context_test.py new file mode 100644 index 00000000000..0d3168e7fe0 --- /dev/null +++ b/cirq-google/cirq_google/api/v2/run_context_test.py @@ -0,0 +1,164 @@ +# Copyright 2024 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cirq_google.api.v2 import program_pb2 +from cirq_google.api.v2 import run_context_pb2 +import cirq_google.api.v2.run_context as run_context +import google.protobuf.text_format as text_format + + +def test_converting_multiple_device_params_to_device_parameters_diff() -> None: + """Test of converting a list of DeviceParameter's to a DeviceParametersDiff object.""" + readout_paths = (["q3_4", "readout_default"], ["q5_6", "readout_default"]) + + device_params = [] + for readout_path in readout_paths: + device_params.extend( + [ + ( + run_context_pb2.DeviceParameter( + path=[*readout_path, "readoutDemodDelay"], units="ns" + ), + program_pb2.ArgValue(float_value=5.0), + ), + ( + run_context_pb2.DeviceParameter(path=[*readout_path, "readoutFidelities"]), + program_pb2.ArgValue( + double_values=program_pb2.RepeatedDouble(values=[0.991, 0.993]) + ), + ), + ( + run_context_pb2.DeviceParameter(path=[*readout_path, "demod", "phase_i_rad"]), + program_pb2.ArgValue(double_value=0.0), + ), + ] + ) + diff = run_context.to_device_parameters_diff(device_params) + expected_diff_pb_text = """ + groups { + parent: -1 + } + groups { + parent: 0 + name: 1 + } + groups { + parent: 1 + name: 4 + } + groups { + parent: -1 + name: 6 + } + groups { + parent: 3 + name: 1 + } + groups { + parent: 4 + name: 4 + } + params { + resource_group: 1 + name: 2 + value { + float_value: 5 + } + } + params { + resource_group: 1 + name: 3 + value { + double_values { + values: 0.991 + values: 0.993 + } + } + } + params { + resource_group: 2 + name: 5 + value { + double_value: 0 + } + } + params { + resource_group: 4 + name: 2 + value { + float_value: 5 + } + } + params { + resource_group: 4 + name: 3 + value { + double_values { + values: 0.991 + values: 0.993 + } + } + } + params { + resource_group: 5 + name: 5 + value { + double_value: 0 + } + } + strs: "q3_4" + strs: "readout_default" + strs: "readoutDemodDelay" + strs: "readoutFidelities" + strs: "demod" + strs: "phase_i_rad" + strs: "q5_6" + """ + print(diff) + assert text_format.Parse(expected_diff_pb_text, run_context_pb2.DeviceParametersDiff()) == diff + + +def test_converting_to_device_parameters_diff_token_id_caching_is_correct() -> None: + """Test that multiple calling of run_context.to_device_parameters_diff gives + correct token id assignments. + """ + device_params = [ + ( + run_context_pb2.DeviceParameter( + path=["q1_2", "readout_default", "readoutDemodDelay"], units="ns" + ), + program_pb2.ArgValue(float_value=5.0), + ) + ] + + diff = run_context.to_device_parameters_diff(device_params) + expected_diff_pb_text = """ + groups { + parent: -1 + } + groups { + name: 1 + } + params { + resource_group: 1 + name: 2 + value { + float_value: 5 + } + } + strs: "q1_2" + strs: "readout_default" + strs: "readoutDemodDelay" + """ + assert text_format.Parse(expected_diff_pb_text, run_context_pb2.DeviceParametersDiff()) == diff