From f93cd08cc4cfd4faefe6af2b5b49a0a907669816 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Tue, 21 Dec 2021 21:53:45 -0800 Subject: [PATCH 01/19] Add Tasks for Discrete Time Crystal Experiments --- recirq/time_crystals/__init__.py | 15 ++ recirq/time_crystals/dtctask.py | 254 +++++++++++++++++++++++++++ recirq/time_crystals/dtctask_test.py | 42 +++++ 3 files changed, 311 insertions(+) create mode 100644 recirq/time_crystals/__init__.py create mode 100644 recirq/time_crystals/dtctask.py create mode 100644 recirq/time_crystals/dtctask_test.py diff --git a/recirq/time_crystals/__init__.py b/recirq/time_crystals/__init__.py new file mode 100644 index 00000000..3c586e9d --- /dev/null +++ b/recirq/time_crystals/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2021 Google +# +# 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 recirq.time_crystals.dtctask import * diff --git a/recirq/time_crystals/dtctask.py b/recirq/time_crystals/dtctask.py new file mode 100644 index 00000000..73f4f810 --- /dev/null +++ b/recirq/time_crystals/dtctask.py @@ -0,0 +1,254 @@ +# Copyright 2021 Google +# +# 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 cirq +import recirq +import datetime +import itertools +import numpy as np +from typing import Sequence, Optional, Dict +import os + +EXPERIMENT_NAME = "time_crystals" +DEFAULT_BASE_DIR = os.path.expanduser(f'~/cirq_results/{EXPERIMENT_NAME}') + + +@recirq.json_serializable_dataclass(namespace='recirq.readout_scan', + registry=recirq.Registry, + frozen=False) +class CompareDTCTask: + """ A task for managing inputs to a comparison Discrete Time Crystal experiment, comparing different options for parameters + + Attributes + dataset_id: unique identifier for this dataset + qubits: chain of connected qubits available for the circuit + cycles: number of DTC cycles to consider for circuits + circuit_list: symbolic DTC circuit list + disorder_instances: number of disorder instances averaged over + options_dict: dict mapping DTCTask attribute names to options for that attribute, to take a product over + options_order: sequence of keys in options_dict, defining order of product over options + """ + + # Task parameters + dataset_id: str + + # experiment parameters + qubits: Sequence[cirq.Qid] + cycles: int + disorder_instances: int + circuit_list: Sequence[cirq.Circuit] + + # options to take product over + options_dict: Dict[str, Sequence[np.ndarray]] + options_order: Sequence[str] + + def __init__( + self, + qubits: Sequence[cirq.Qid], + cycles: int, + disorder_instances: int, + options_dict: Dict[str, Sequence[np.ndarray]], + options_order: Optional[Sequence[str]] = None): + + self.dataset_id = datetime.datetime.utcnow() + + self.qubits = qubits + self.cycles = cycles + self.disorder_instances = disorder_instances + + # create symbolic circuit list from qubits and cycles count + self.circuit_list = recirq.time_crystals.symbolic_dtc_circuit_list(qubits, cycles) + + self.options_dict = options_dict + self.options_order = list(self.options_dict.keys()) if options_order is None else options_order + + # check that the input parameters are consistent + assert set(self.options_order) == set(self.options_dict.keys()), 'options_order and the keys of options_dict are not the same' + assert not {'initial_states', 'initial_state'} <= self.options_dict.keys(), 'do not supply both initial_states and initial_state' + + + @property + def fn(self): + fn = (f'{self.dataset_id}/' + f'{len(self.qubits)}/' + f'{self.cycles}/' + f'{self.disorder_instances}/' + f'{self.options_dict}') + return fn + + + def dtctasks(self): + """ Yield a sequence of DTCTasks that are the product of the options in self.options_dict. + All DTCTask attributes not in options_dict are taken to be their default values + Yields: + DTCTasks with parameters taken from self.options_dict + """ + + # take product over elements of options_dict, in the order of options_order + for components in itertools.product(*(self.options_dict[attribute_name] for attribute_name in self.options_order)): + # prepare arguments for DTCTask + kwargs = dict(zip(self.options_order, components)) + yield DTCTask(qubits=self.qubits, disorder_instances=self.disorder_instances, **kwargs) + + +@recirq.json_serializable_dataclass(namespace='recirq.readout_scan', + registry=recirq.Registry, + frozen=False) +class DTCTask: + """ A task for managing inputs to a Discrete Time Crystal experiment, over some number of disorder instances + + Attributes: + dataset_id: unique identifier for this dataset + qubits: a chain of connected qubits available for the circuit + disorder_instances: number of disorder instances averaged over + initial_states: initial state of the system used in circuit + g: thermalization constant used in circuit + local_fields: random noise used in circuit + thetas: theta parameters for FSim Gate used in circuit + zetas: zeta parameters for FSim Gate used in circuit + chis: chi parameters for FSim Gate used in circuit + phis: phi parameters for FSim Gate used in circuit + gammas: gamma parameters for FSim Gate used in circuit + + """ + # Task parameters + dataset_id: str + + # experiment parameters + qubits: Sequence[cirq.Qid] + disorder_instances: int + + # FSim Gate parameters + # ndarrays in this section are in shape (disorder_instances, len(qubits) - 1) + g: int + initial_states: np.ndarray + local_fields: np.ndarray + + # FSim Gate Parameters + # ndarrays in this section are in shape (disorder_instances, len(qubits) - 1) + thetas: np.ndarray + zetas: np.ndarray + chis: np.ndarray + gammas: np.ndarray + phis: np.ndarray + + + def __init__( + self, + qubits: Optional[Sequence[cirq.Qid]] = None, + disorder_instances: Optional[int] = None, + g: Optional[int] = None, + initial_state: Optional[np.ndarray] = None, + initial_states: Optional[np.ndarray] = None, + local_fields: Optional[np.ndarray] = None, + thetas: Optional[np.ndarray] = None, + zetas: Optional[np.ndarray] = None, + chis: Optional[np.ndarray] = None, + gammas: Optional[np.ndarray] = None, + phis: Optional[np.ndarray] = None + ): + + self.dataset_id = datetime.datetime.utcnow() + + self.disorder_instances = 36 if disorder_instances is None else disorder_instances + + self.g = 0.94 if g is None else g + + if qubits is None: + qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), (6, 6), (6, 5), (7, 5), (8, 5), (8, 4), (8, 3), (7, 3), (6, 3)] + self.qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] + else: + self.qubits = qubits + + num_qubits = len(self.qubits) + + # only enable use of initial_state or initial_states + assert initial_state is None or initial_states is None, 'do not supply both initial_state and initial_states' + if initial_state is None and initial_states is None: + self.initial_states = np.random.choice(2, (self.disorder_instances, num_qubits)) + elif initial_states is None: + assert len(initial_state) == num_qubits, f'initial_state is of shape {str(len(initial_state))}, not (num_qubits,)' + self.initial_states = np.tile(initial_state, (self.disorder_instances, 1)) + elif initial_state is None: + assert initial_states.shape == (self.disorder_instances, num_qubits), f'initial_states is of shape {initial_states.shape}, not (disorder_instances, num_qubits)' + self.initial_states = initial_states + + if local_fields is None: + self.local_fields = np.random.uniform(-1.0, 1.0, (self.disorder_instances, num_qubits)) + else: + assert local_fields.shape == (self.disorder_instances, num_qubits), f'local_fields is of shape {local_fields.shape}, not (disorder_instnaces, num_qubits)' + self.local_fields = local_fields + + zero_params = [thetas, zetas, chis] + for index, zero_param in enumerate(zero_params): + if zero_param is None: + zero_params[index] = np.zeros((self.disorder_instances, num_qubits - 1)) + else: + assert zero_param.shape == (self.disorder_instances, num_qubits - 1), f'thetas, zetas or chis is of shape {zero_param.shape}, not (disorder_instances, num_qubits - 1)' + self.thetas, self.zetas, self.chis = zero_params + + # if gamma or phi is not supplied, generate it from the other such that phis == -2*gammas + if gammas is None and phis is None: + self.gammas = -np.random.uniform(0.5*np.pi, 1.5*np.pi, (self.disorder_instances, num_qubits - 1)) + self.phis = -2*self.gammas + elif phis is None: + assert gammas.shape == (self.disorder_instances, num_qubits - 1), f'gammas is of shape {gammas.shape}, not (disorder_instances, num_qubits - 1)' + self.gammas = gammas + self.phis = -2*self.gammas + elif gammas is None: + assert phis.shape == (self.disorder_instances, num_qubits - 1), f'phis is of shape {phis.shape}, not (disorder_instances, num_qubits - 1)' + self.phis = phis + self.gammas = -1/2*self.phis + else: + assert gammas.shape == (self.disorder_instances, num_qubits - 1), f'gammas is of shape {gammas.shape}, not (disorder_instances, num_qubits - 1)' + assert phis.shape == (self.disorder_instances, num_qubits - 1), f'phis is of shape {phis.shape}, not (disorder_instances, num_qubits - 1)' + self.phis = phis + self.gammas = gammas + + + @property + def fn(self): + fn = (f'{self.dataset_id}/' + f'{self.qubits}/' + f'{self.disorder_instances}/' + f'{self.g}/' + f'{self.initial_states}/' + f'{self.local_fields}/' + f'{self.thetas}/' + f'{self.zetas}/' + f'{self.chis}/' + f'{self.gammas}/' + f'{self.phis}/') + return fn + + + def param_resolvers(self): + """ return a sweep over param resolvers for the parameters of this task + Returns: + `cirq.Zip` object with self.disorder_instances many `cirq.ParamResolver`s + """ + + # initialize the dict and add the first, non-qubit-dependent parameter, g + factor_dict = {'g': np.full(self.disorder_instances, self.g).tolist()} + + # iterate over the different parameters + qubit_varying_factors = ["initial_states", "local_fields", "thetas", "zetas", "chis", "gammas", "phis"] + for factor in qubit_varying_factors: + factor_options = getattr(self, factor) + # iterate over each index in the qubit chain and the various options for that qubit + for index, qubit_factor_options in enumerate(factor_options.transpose()): + factor_name = factor[:-1] + factor_dict[f'{factor_name}_{index}'] = qubit_factor_options.tolist() + + return cirq.study.dict_to_zip_sweep(factor_dict) diff --git a/recirq/time_crystals/dtctask_test.py b/recirq/time_crystals/dtctask_test.py new file mode 100644 index 00000000..8084cd24 --- /dev/null +++ b/recirq/time_crystals/dtctask_test.py @@ -0,0 +1,42 @@ +# Copyright 2021 Google +# +# 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 recirq.time_crystals as time_crystals +import cirq +import numpy as np + +def test_DTCTask(): + np.random.seed(5) + qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), (6, 6), (6, 5), (7, 5), (8, 5), + (8, 4), (8, 3), (7, 3), (6, 3)] + + qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] + num_qubits = len(qubits) + g = 0.94 + instances = 36 + initial_state = np.random.choice(2, num_qubits) + local_fields = np.random.uniform(-1.0, 1.0, (instances, num_qubits)) + thetas = np.zeros((instances, num_qubits - 1)) + zetas = np.zeros((instances, num_qubits - 1)) + chis = np.zeros((instances, num_qubits - 1)) + gammas = -np.random.uniform(0.5*np.pi, 1.5*np.pi, (instances, num_qubits - 1)) + phis = -2*gammas + args = ['qubits', 'g', 'initial_state', 'local_fields', 'thetas', 'zetas', 'chis', 'gammas', 'phis'] + default_resolvers = time_crystals.DTCTask().param_resolvers() + for arg in args: + kwargs = {} + for name in args: + kwargs[name] = None if name is arg else locals()[name] + dtctask = time_crystals.DTCTask(disorder_instances=instances, **kwargs) + param_resolvers = dtctask.param_resolvers() From 08ef8a75ed2c783931480f841a1ae2041bf0167c Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 14 Jan 2022 03:29:29 -0800 Subject: [PATCH 02/19] Fix import and dataclass decorators --- recirq/time_crystals/__init__.py | 2 +- recirq/time_crystals/dtctask.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/recirq/time_crystals/__init__.py b/recirq/time_crystals/__init__.py index 3c586e9d..15b34df3 100644 --- a/recirq/time_crystals/__init__.py +++ b/recirq/time_crystals/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -from recirq.time_crystals.dtctask import * +from recirq.time_crystals.dtctask import DTCTask, CompareDTCTask diff --git a/recirq/time_crystals/dtctask.py b/recirq/time_crystals/dtctask.py index 73f4f810..00d2cd33 100644 --- a/recirq/time_crystals/dtctask.py +++ b/recirq/time_crystals/dtctask.py @@ -24,11 +24,11 @@ DEFAULT_BASE_DIR = os.path.expanduser(f'~/cirq_results/{EXPERIMENT_NAME}') -@recirq.json_serializable_dataclass(namespace='recirq.readout_scan', +@recirq.json_serializable_dataclass(namespace='recirq.time_crystals', registry=recirq.Registry, frozen=False) class CompareDTCTask: - """ A task for managing inputs to a comparison Discrete Time Crystal experiment, comparing different options for parameters + """ A task to a comparison DTC experiment, comparing different options for parameters Attributes dataset_id: unique identifier for this dataset @@ -102,7 +102,7 @@ def dtctasks(self): yield DTCTask(qubits=self.qubits, disorder_instances=self.disorder_instances, **kwargs) -@recirq.json_serializable_dataclass(namespace='recirq.readout_scan', +@recirq.json_serializable_dataclass(namespace='recirq.time_crystals', registry=recirq.Registry, frozen=False) class DTCTask: From e5d91c7befa6b4f9c92434806ac5f51a1a3d27a8 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 14 Jan 2022 09:56:43 -0800 Subject: [PATCH 03/19] Full removal of tasks and dataclasses --- recirq/time_crystals/__init__.py | 4 +- recirq/time_crystals/dtcexperiment.py | 144 ++++++++++++ recirq/time_crystals/dtcexperiment_test.py | 66 ++++++ recirq/time_crystals/dtctask.py | 254 --------------------- recirq/time_crystals/dtctask_test.py | 42 ---- 5 files changed, 213 insertions(+), 297 deletions(-) create mode 100644 recirq/time_crystals/dtcexperiment.py create mode 100644 recirq/time_crystals/dtcexperiment_test.py delete mode 100644 recirq/time_crystals/dtctask.py delete mode 100644 recirq/time_crystals/dtctask_test.py diff --git a/recirq/time_crystals/__init__.py b/recirq/time_crystals/__init__.py index 15b34df3..6cdbb384 100644 --- a/recirq/time_crystals/__init__.py +++ b/recirq/time_crystals/__init__.py @@ -12,4 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from recirq.time_crystals.dtctask import DTCTask, CompareDTCTask +from recirq.time_crystals.dtcexperiment import (DTCExperiment, comparison_experiments, + EXPERIMENT_NAME, DEFAULT_BASE_DIR) + diff --git a/recirq/time_crystals/dtcexperiment.py b/recirq/time_crystals/dtcexperiment.py new file mode 100644 index 00000000..dddc492f --- /dev/null +++ b/recirq/time_crystals/dtcexperiment.py @@ -0,0 +1,144 @@ +# Copyright 2021 Google +# +# 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 cirq +import itertools +import numpy as np +from typing import Sequence, Optional, Generator +import os + +EXPERIMENT_NAME = "time_crystals" +DEFAULT_BASE_DIR = os.path.expanduser(f'~/cirq_results/{EXPERIMENT_NAME}') + +class DTCExperiment: + """ Manage inputs to a DTC experiment, over some number of disorder instances + + Attributes: + qubits: a chain of connected qubits available for the circuit + disorder_instances: number of disorder instances averaged over + initial_states: initial state of the system used in circuit + g: thermalization constant used in circuit + local_fields: random noise used in circuit + thetas: theta parameters for FSim Gate used in circuit + zetas: zeta parameters for FSim Gate used in circuit + chis: chi parameters for FSim Gate used in circuit + phis: phi parameters for FSim Gate used in circuit + gammas: gamma parameters for FSim Gate used in circuit + + """ + + def __init__( + self, + qubits: Optional[Sequence[cirq.Qid]] = None, + disorder_instances: Optional[int] = 36, + g: Optional[int] = 0.94, + initial_states: Optional[np.ndarray] = None, + local_fields: Optional[np.ndarray] = None, + thetas: Optional[np.ndarray] = None, + zetas: Optional[np.ndarray] = None, + chis: Optional[np.ndarray] = None, + gammas: Optional[np.ndarray] = None, + phis: Optional[np.ndarray] = None + ): + + self.qubits = qubits + self.disorder_instances = disorder_instances + self.g = g + + if qubits is None: + qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), + (6, 6), (6, 5), (7, 5), (8, 5), (8, 4), (8, 3), (7, 3), (6, 3)] + self.qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] + else: + self.qubits = qubits + + num_qubits = len(self.qubits) + + if initial_states is None: + self.initial_states = np.random.choice(2, (self.disorder_instances, num_qubits)) + else: + self.initial_states = initial_states + + if local_fields is None: + self.local_fields = np.random.uniform(-1.0, 1.0, (self.disorder_instances, num_qubits)) + else: + self.local_fields = local_fields + + zero_params = [thetas, zetas, chis] + for index, zero_param in enumerate(zero_params): + if zero_param is None: + zero_params[index] = np.zeros((self.disorder_instances, num_qubits - 1)) + else: + zero_params[index] = zero_param + self.thetas, self.zetas, self.chis = zero_params + + # if gamma or phi is not supplied, generate it from the other such that phis == -2*gammas + if gammas is None and phis is None: + self.gammas = -np.random.uniform(0.5*np.pi, 1.5*np.pi, + (self.disorder_instances, num_qubits - 1)) + self.phis = -2*self.gammas + elif phis is None: + self.gammas = gammas + self.phis = -2*self.gammas + elif gammas is None: + self.phis = phis + self.gammas = -1/2*self.phis + else: + self.phis = phis + self.gammas = gammas + + def param_resolvers(self) -> cirq.Zip: + """ return a sweep over disorder instances for the parameters of this experiment + Returns: + `cirq.Zip` object with self.disorder_instances many `cirq.ParamResolver`s + """ + + # initialize the dict and add the first, non-qubit-dependent parameter, g + factor_dict = {'g': np.full(self.disorder_instances, self.g).tolist()} + + # iterate over the different parameters + qubit_varying_factors = ["initial_states", "local_fields", "thetas", + "zetas", "chis", "gammas", "phis"] + for factor in qubit_varying_factors: + parameter = getattr(self, factor) + # iterate over each index in the qubit chain and the various options for that qubit + for index, qubit_factor_options in enumerate(parameter.transpose()): + factor_name = factor[:-1] + factor_dict[f'{factor_name}_{index}'] = qubit_factor_options.tolist() + + return cirq.study.dict_to_zip_sweep(factor_dict) + +def comparison_experiments(qubits: Sequence[cirq.Qid], + disorder_instances: int, + g_cases: Optional[Sequence[int]] = None, + initial_states_cases: Optional[Sequence[np.ndarray]] = None, + local_fields_cases: Optional[Sequence[np.ndarray]] = None, + phis_cases: Optional[Sequence[np.ndarray]] = None, + ) -> Generator[DTCExperiment, None, None]: + """ Yield DTCExperiments with parameters taken from the cartesian product of input parameters + Args: + Any number of (parameter, parameter_values) pairs + Yields: + DTCTasks with parameters taken from self.options_dict + """ + + # take product over elements of options_dict, in the order of options_order + items = [([x] if x is None else x) + for x in [g_cases, initial_states_cases, local_fields_cases, phis_cases]] + for components in itertools.product(*items): + # prepare arguments for DTCExperiment + kwargs = dict(zip(['g', 'initial_states', 'local_fields', 'phis'], components)) + yield DTCExperiment(qubits=qubits, + disorder_instances=disorder_instances, + **kwargs) diff --git a/recirq/time_crystals/dtcexperiment_test.py b/recirq/time_crystals/dtcexperiment_test.py new file mode 100644 index 00000000..a4ed673e --- /dev/null +++ b/recirq/time_crystals/dtcexperiment_test.py @@ -0,0 +1,66 @@ +# Copyright 2021 Google +# +# 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 recirq.time_crystals as time_crystals +import cirq +import numpy as np +import itertools + +def test_DTCExperiment(): + np.random.seed(5) + qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), (6, 6), + (6, 5), (7, 5), (8, 5), (8, 4), (8, 3), (7, 3), (6, 3)] + + qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] + num_qubits = len(qubits) + g = 0.94 + instances = 36 + initial_state = np.random.choice(2, num_qubits) + local_fields = np.random.uniform(-1.0, 1.0, (instances, num_qubits)) + thetas = np.zeros((instances, num_qubits - 1)) + zetas = np.zeros((instances, num_qubits - 1)) + chis = np.zeros((instances, num_qubits - 1)) + gammas = -np.random.uniform(0.5*np.pi, 1.5*np.pi, (instances, num_qubits - 1)) + phis = -2*gammas + args = ['qubits', 'g', 'initial_state', 'local_fields', + 'thetas', 'zetas', 'chis', 'gammas', 'phis'] + default_resolvers = time_crystals.DTCExperiment().param_resolvers() + for arg in args: + kwargs = {} + for name in args: + kwargs[name] = None if name is arg else locals()[name] + dtcexperiment = time_crystals.DTCExperiment(disorder_instances=instances, **kwargs) + param_resolvers = dtcexperiment.param_resolvers() + +def test_comparison_experiments(): + np.random.seed(5) + qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), (6, 6), + (6, 5), (7, 5), (8, 5), (8, 4), (8, 3), (7, 3), (6, 3)] + + qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] + num_qubits = len(qubits) + g_cases = [0.94, 0.6] + instances = 36 + initial_states_cases = [np.random.choice(2, (instances, num_qubits)), + np.tile(np.random.choice(2, num_qubits), (instances,1))] + local_fields_cases = [np.random.uniform(-1.0, 1.0, (instances, num_qubits)), + np.tile(np.random.uniform(-1.0, 1.0, num_qubits), (instances,1))] + phis_cases = [np.random.uniform(np.pi, 3*np.pi, (instances, num_qubits - 1)), + np.full((instances, num_qubits - 1), 0.4)] + names = ['g_cases', 'initial_states_cases', 'local_fields_cases', 'phis_cases'] + args = [g_cases, initial_states_cases, local_fields_cases, phis_cases] + for cases in itertools.product(*zip([None]*4, args)): + kwargs = dict(zip(names, cases)) + for experiment in time_crystals.comparison_experiments(qubits, instances, **kwargs): + param_resolvers = experiment.param_resolvers() diff --git a/recirq/time_crystals/dtctask.py b/recirq/time_crystals/dtctask.py deleted file mode 100644 index 00d2cd33..00000000 --- a/recirq/time_crystals/dtctask.py +++ /dev/null @@ -1,254 +0,0 @@ -# Copyright 2021 Google -# -# 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 cirq -import recirq -import datetime -import itertools -import numpy as np -from typing import Sequence, Optional, Dict -import os - -EXPERIMENT_NAME = "time_crystals" -DEFAULT_BASE_DIR = os.path.expanduser(f'~/cirq_results/{EXPERIMENT_NAME}') - - -@recirq.json_serializable_dataclass(namespace='recirq.time_crystals', - registry=recirq.Registry, - frozen=False) -class CompareDTCTask: - """ A task to a comparison DTC experiment, comparing different options for parameters - - Attributes - dataset_id: unique identifier for this dataset - qubits: chain of connected qubits available for the circuit - cycles: number of DTC cycles to consider for circuits - circuit_list: symbolic DTC circuit list - disorder_instances: number of disorder instances averaged over - options_dict: dict mapping DTCTask attribute names to options for that attribute, to take a product over - options_order: sequence of keys in options_dict, defining order of product over options - """ - - # Task parameters - dataset_id: str - - # experiment parameters - qubits: Sequence[cirq.Qid] - cycles: int - disorder_instances: int - circuit_list: Sequence[cirq.Circuit] - - # options to take product over - options_dict: Dict[str, Sequence[np.ndarray]] - options_order: Sequence[str] - - def __init__( - self, - qubits: Sequence[cirq.Qid], - cycles: int, - disorder_instances: int, - options_dict: Dict[str, Sequence[np.ndarray]], - options_order: Optional[Sequence[str]] = None): - - self.dataset_id = datetime.datetime.utcnow() - - self.qubits = qubits - self.cycles = cycles - self.disorder_instances = disorder_instances - - # create symbolic circuit list from qubits and cycles count - self.circuit_list = recirq.time_crystals.symbolic_dtc_circuit_list(qubits, cycles) - - self.options_dict = options_dict - self.options_order = list(self.options_dict.keys()) if options_order is None else options_order - - # check that the input parameters are consistent - assert set(self.options_order) == set(self.options_dict.keys()), 'options_order and the keys of options_dict are not the same' - assert not {'initial_states', 'initial_state'} <= self.options_dict.keys(), 'do not supply both initial_states and initial_state' - - - @property - def fn(self): - fn = (f'{self.dataset_id}/' - f'{len(self.qubits)}/' - f'{self.cycles}/' - f'{self.disorder_instances}/' - f'{self.options_dict}') - return fn - - - def dtctasks(self): - """ Yield a sequence of DTCTasks that are the product of the options in self.options_dict. - All DTCTask attributes not in options_dict are taken to be their default values - Yields: - DTCTasks with parameters taken from self.options_dict - """ - - # take product over elements of options_dict, in the order of options_order - for components in itertools.product(*(self.options_dict[attribute_name] for attribute_name in self.options_order)): - # prepare arguments for DTCTask - kwargs = dict(zip(self.options_order, components)) - yield DTCTask(qubits=self.qubits, disorder_instances=self.disorder_instances, **kwargs) - - -@recirq.json_serializable_dataclass(namespace='recirq.time_crystals', - registry=recirq.Registry, - frozen=False) -class DTCTask: - """ A task for managing inputs to a Discrete Time Crystal experiment, over some number of disorder instances - - Attributes: - dataset_id: unique identifier for this dataset - qubits: a chain of connected qubits available for the circuit - disorder_instances: number of disorder instances averaged over - initial_states: initial state of the system used in circuit - g: thermalization constant used in circuit - local_fields: random noise used in circuit - thetas: theta parameters for FSim Gate used in circuit - zetas: zeta parameters for FSim Gate used in circuit - chis: chi parameters for FSim Gate used in circuit - phis: phi parameters for FSim Gate used in circuit - gammas: gamma parameters for FSim Gate used in circuit - - """ - # Task parameters - dataset_id: str - - # experiment parameters - qubits: Sequence[cirq.Qid] - disorder_instances: int - - # FSim Gate parameters - # ndarrays in this section are in shape (disorder_instances, len(qubits) - 1) - g: int - initial_states: np.ndarray - local_fields: np.ndarray - - # FSim Gate Parameters - # ndarrays in this section are in shape (disorder_instances, len(qubits) - 1) - thetas: np.ndarray - zetas: np.ndarray - chis: np.ndarray - gammas: np.ndarray - phis: np.ndarray - - - def __init__( - self, - qubits: Optional[Sequence[cirq.Qid]] = None, - disorder_instances: Optional[int] = None, - g: Optional[int] = None, - initial_state: Optional[np.ndarray] = None, - initial_states: Optional[np.ndarray] = None, - local_fields: Optional[np.ndarray] = None, - thetas: Optional[np.ndarray] = None, - zetas: Optional[np.ndarray] = None, - chis: Optional[np.ndarray] = None, - gammas: Optional[np.ndarray] = None, - phis: Optional[np.ndarray] = None - ): - - self.dataset_id = datetime.datetime.utcnow() - - self.disorder_instances = 36 if disorder_instances is None else disorder_instances - - self.g = 0.94 if g is None else g - - if qubits is None: - qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), (6, 6), (6, 5), (7, 5), (8, 5), (8, 4), (8, 3), (7, 3), (6, 3)] - self.qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] - else: - self.qubits = qubits - - num_qubits = len(self.qubits) - - # only enable use of initial_state or initial_states - assert initial_state is None or initial_states is None, 'do not supply both initial_state and initial_states' - if initial_state is None and initial_states is None: - self.initial_states = np.random.choice(2, (self.disorder_instances, num_qubits)) - elif initial_states is None: - assert len(initial_state) == num_qubits, f'initial_state is of shape {str(len(initial_state))}, not (num_qubits,)' - self.initial_states = np.tile(initial_state, (self.disorder_instances, 1)) - elif initial_state is None: - assert initial_states.shape == (self.disorder_instances, num_qubits), f'initial_states is of shape {initial_states.shape}, not (disorder_instances, num_qubits)' - self.initial_states = initial_states - - if local_fields is None: - self.local_fields = np.random.uniform(-1.0, 1.0, (self.disorder_instances, num_qubits)) - else: - assert local_fields.shape == (self.disorder_instances, num_qubits), f'local_fields is of shape {local_fields.shape}, not (disorder_instnaces, num_qubits)' - self.local_fields = local_fields - - zero_params = [thetas, zetas, chis] - for index, zero_param in enumerate(zero_params): - if zero_param is None: - zero_params[index] = np.zeros((self.disorder_instances, num_qubits - 1)) - else: - assert zero_param.shape == (self.disorder_instances, num_qubits - 1), f'thetas, zetas or chis is of shape {zero_param.shape}, not (disorder_instances, num_qubits - 1)' - self.thetas, self.zetas, self.chis = zero_params - - # if gamma or phi is not supplied, generate it from the other such that phis == -2*gammas - if gammas is None and phis is None: - self.gammas = -np.random.uniform(0.5*np.pi, 1.5*np.pi, (self.disorder_instances, num_qubits - 1)) - self.phis = -2*self.gammas - elif phis is None: - assert gammas.shape == (self.disorder_instances, num_qubits - 1), f'gammas is of shape {gammas.shape}, not (disorder_instances, num_qubits - 1)' - self.gammas = gammas - self.phis = -2*self.gammas - elif gammas is None: - assert phis.shape == (self.disorder_instances, num_qubits - 1), f'phis is of shape {phis.shape}, not (disorder_instances, num_qubits - 1)' - self.phis = phis - self.gammas = -1/2*self.phis - else: - assert gammas.shape == (self.disorder_instances, num_qubits - 1), f'gammas is of shape {gammas.shape}, not (disorder_instances, num_qubits - 1)' - assert phis.shape == (self.disorder_instances, num_qubits - 1), f'phis is of shape {phis.shape}, not (disorder_instances, num_qubits - 1)' - self.phis = phis - self.gammas = gammas - - - @property - def fn(self): - fn = (f'{self.dataset_id}/' - f'{self.qubits}/' - f'{self.disorder_instances}/' - f'{self.g}/' - f'{self.initial_states}/' - f'{self.local_fields}/' - f'{self.thetas}/' - f'{self.zetas}/' - f'{self.chis}/' - f'{self.gammas}/' - f'{self.phis}/') - return fn - - - def param_resolvers(self): - """ return a sweep over param resolvers for the parameters of this task - Returns: - `cirq.Zip` object with self.disorder_instances many `cirq.ParamResolver`s - """ - - # initialize the dict and add the first, non-qubit-dependent parameter, g - factor_dict = {'g': np.full(self.disorder_instances, self.g).tolist()} - - # iterate over the different parameters - qubit_varying_factors = ["initial_states", "local_fields", "thetas", "zetas", "chis", "gammas", "phis"] - for factor in qubit_varying_factors: - factor_options = getattr(self, factor) - # iterate over each index in the qubit chain and the various options for that qubit - for index, qubit_factor_options in enumerate(factor_options.transpose()): - factor_name = factor[:-1] - factor_dict[f'{factor_name}_{index}'] = qubit_factor_options.tolist() - - return cirq.study.dict_to_zip_sweep(factor_dict) diff --git a/recirq/time_crystals/dtctask_test.py b/recirq/time_crystals/dtctask_test.py deleted file mode 100644 index 8084cd24..00000000 --- a/recirq/time_crystals/dtctask_test.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2021 Google -# -# 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 recirq.time_crystals as time_crystals -import cirq -import numpy as np - -def test_DTCTask(): - np.random.seed(5) - qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), (6, 6), (6, 5), (7, 5), (8, 5), - (8, 4), (8, 3), (7, 3), (6, 3)] - - qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] - num_qubits = len(qubits) - g = 0.94 - instances = 36 - initial_state = np.random.choice(2, num_qubits) - local_fields = np.random.uniform(-1.0, 1.0, (instances, num_qubits)) - thetas = np.zeros((instances, num_qubits - 1)) - zetas = np.zeros((instances, num_qubits - 1)) - chis = np.zeros((instances, num_qubits - 1)) - gammas = -np.random.uniform(0.5*np.pi, 1.5*np.pi, (instances, num_qubits - 1)) - phis = -2*gammas - args = ['qubits', 'g', 'initial_state', 'local_fields', 'thetas', 'zetas', 'chis', 'gammas', 'phis'] - default_resolvers = time_crystals.DTCTask().param_resolvers() - for arg in args: - kwargs = {} - for name in args: - kwargs[name] = None if name is arg else locals()[name] - dtctask = time_crystals.DTCTask(disorder_instances=instances, **kwargs) - param_resolvers = dtctask.param_resolvers() From bd8cf76186b4f8b3ea223fed5dc692a89cd1a2fc Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Mon, 24 Jan 2022 19:32:53 -0800 Subject: [PATCH 04/19] Run `black` python formatter --- recirq/time_crystals/__init__.py | 9 +- recirq/time_crystals/dtcexperiment.py | 117 +++++++++++++-------- recirq/time_crystals/dtcexperiment_test.py | 89 ++++++++++++---- 3 files changed, 152 insertions(+), 63 deletions(-) diff --git a/recirq/time_crystals/__init__.py b/recirq/time_crystals/__init__.py index 6cdbb384..3c9c580e 100644 --- a/recirq/time_crystals/__init__.py +++ b/recirq/time_crystals/__init__.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from recirq.time_crystals.dtcexperiment import (DTCExperiment, comparison_experiments, - EXPERIMENT_NAME, DEFAULT_BASE_DIR) - +from recirq.time_crystals.dtcexperiment import ( + DTCExperiment, + comparison_experiments, + EXPERIMENT_NAME, + DEFAULT_BASE_DIR, +) diff --git a/recirq/time_crystals/dtcexperiment.py b/recirq/time_crystals/dtcexperiment.py index dddc492f..899882c9 100644 --- a/recirq/time_crystals/dtcexperiment.py +++ b/recirq/time_crystals/dtcexperiment.py @@ -19,10 +19,11 @@ import os EXPERIMENT_NAME = "time_crystals" -DEFAULT_BASE_DIR = os.path.expanduser(f'~/cirq_results/{EXPERIMENT_NAME}') +DEFAULT_BASE_DIR = os.path.expanduser(f"~/cirq_results/{EXPERIMENT_NAME}") + class DTCExperiment: - """ Manage inputs to a DTC experiment, over some number of disorder instances + """Manage inputs to a DTC experiment, over some number of disorder instances Attributes: qubits: a chain of connected qubits available for the circuit @@ -39,26 +40,42 @@ class DTCExperiment: """ def __init__( - self, - qubits: Optional[Sequence[cirq.Qid]] = None, - disorder_instances: Optional[int] = 36, - g: Optional[int] = 0.94, - initial_states: Optional[np.ndarray] = None, - local_fields: Optional[np.ndarray] = None, - thetas: Optional[np.ndarray] = None, - zetas: Optional[np.ndarray] = None, - chis: Optional[np.ndarray] = None, - gammas: Optional[np.ndarray] = None, - phis: Optional[np.ndarray] = None - ): + self, + qubits: Optional[Sequence[cirq.Qid]] = None, + disorder_instances: Optional[int] = 36, + g: Optional[int] = 0.94, + initial_states: Optional[np.ndarray] = None, + local_fields: Optional[np.ndarray] = None, + thetas: Optional[np.ndarray] = None, + zetas: Optional[np.ndarray] = None, + chis: Optional[np.ndarray] = None, + gammas: Optional[np.ndarray] = None, + phis: Optional[np.ndarray] = None, + ): self.qubits = qubits self.disorder_instances = disorder_instances self.g = g if qubits is None: - qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), - (6, 6), (6, 5), (7, 5), (8, 5), (8, 4), (8, 3), (7, 3), (6, 3)] + qubit_locations = [ + (3, 9), + (3, 8), + (3, 7), + (4, 7), + (4, 8), + (5, 8), + (5, 7), + (5, 6), + (6, 6), + (6, 5), + (7, 5), + (8, 5), + (8, 4), + (8, 3), + (7, 3), + (6, 3), + ] self.qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] else: self.qubits = qubits @@ -66,12 +83,16 @@ def __init__( num_qubits = len(self.qubits) if initial_states is None: - self.initial_states = np.random.choice(2, (self.disorder_instances, num_qubits)) + self.initial_states = np.random.choice( + 2, (self.disorder_instances, num_qubits) + ) else: self.initial_states = initial_states if local_fields is None: - self.local_fields = np.random.uniform(-1.0, 1.0, (self.disorder_instances, num_qubits)) + self.local_fields = np.random.uniform( + -1.0, 1.0, (self.disorder_instances, num_qubits) + ) else: self.local_fields = local_fields @@ -85,48 +106,58 @@ def __init__( # if gamma or phi is not supplied, generate it from the other such that phis == -2*gammas if gammas is None and phis is None: - self.gammas = -np.random.uniform(0.5*np.pi, 1.5*np.pi, - (self.disorder_instances, num_qubits - 1)) - self.phis = -2*self.gammas + self.gammas = -np.random.uniform( + 0.5 * np.pi, 1.5 * np.pi, (self.disorder_instances, num_qubits - 1) + ) + self.phis = -2 * self.gammas elif phis is None: self.gammas = gammas - self.phis = -2*self.gammas + self.phis = -2 * self.gammas elif gammas is None: self.phis = phis - self.gammas = -1/2*self.phis + self.gammas = -1 / 2 * self.phis else: self.phis = phis self.gammas = gammas def param_resolvers(self) -> cirq.Zip: - """ return a sweep over disorder instances for the parameters of this experiment + """return a sweep over disorder instances for the parameters of this experiment Returns: `cirq.Zip` object with self.disorder_instances many `cirq.ParamResolver`s """ # initialize the dict and add the first, non-qubit-dependent parameter, g - factor_dict = {'g': np.full(self.disorder_instances, self.g).tolist()} + factor_dict = {"g": np.full(self.disorder_instances, self.g).tolist()} # iterate over the different parameters - qubit_varying_factors = ["initial_states", "local_fields", "thetas", - "zetas", "chis", "gammas", "phis"] + qubit_varying_factors = [ + "initial_states", + "local_fields", + "thetas", + "zetas", + "chis", + "gammas", + "phis", + ] for factor in qubit_varying_factors: parameter = getattr(self, factor) # iterate over each index in the qubit chain and the various options for that qubit for index, qubit_factor_options in enumerate(parameter.transpose()): factor_name = factor[:-1] - factor_dict[f'{factor_name}_{index}'] = qubit_factor_options.tolist() + factor_dict[f"{factor_name}_{index}"] = qubit_factor_options.tolist() return cirq.study.dict_to_zip_sweep(factor_dict) -def comparison_experiments(qubits: Sequence[cirq.Qid], - disorder_instances: int, - g_cases: Optional[Sequence[int]] = None, - initial_states_cases: Optional[Sequence[np.ndarray]] = None, - local_fields_cases: Optional[Sequence[np.ndarray]] = None, - phis_cases: Optional[Sequence[np.ndarray]] = None, - ) -> Generator[DTCExperiment, None, None]: - """ Yield DTCExperiments with parameters taken from the cartesian product of input parameters + +def comparison_experiments( + qubits: Sequence[cirq.Qid], + disorder_instances: int, + g_cases: Optional[Sequence[int]] = None, + initial_states_cases: Optional[Sequence[np.ndarray]] = None, + local_fields_cases: Optional[Sequence[np.ndarray]] = None, + phis_cases: Optional[Sequence[np.ndarray]] = None, +) -> Generator[DTCExperiment, None, None]: + """Yield DTCExperiments with parameters taken from the cartesian product of input parameters Args: Any number of (parameter, parameter_values) pairs Yields: @@ -134,11 +165,13 @@ def comparison_experiments(qubits: Sequence[cirq.Qid], """ # take product over elements of options_dict, in the order of options_order - items = [([x] if x is None else x) - for x in [g_cases, initial_states_cases, local_fields_cases, phis_cases]] + items = [ + ([x] if x is None else x) + for x in [g_cases, initial_states_cases, local_fields_cases, phis_cases] + ] for components in itertools.product(*items): # prepare arguments for DTCExperiment - kwargs = dict(zip(['g', 'initial_states', 'local_fields', 'phis'], components)) - yield DTCExperiment(qubits=qubits, - disorder_instances=disorder_instances, - **kwargs) + kwargs = dict(zip(["g", "initial_states", "local_fields", "phis"], components)) + yield DTCExperiment( + qubits=qubits, disorder_instances=disorder_instances, **kwargs + ) diff --git a/recirq/time_crystals/dtcexperiment_test.py b/recirq/time_crystals/dtcexperiment_test.py index a4ed673e..12f6658a 100644 --- a/recirq/time_crystals/dtcexperiment_test.py +++ b/recirq/time_crystals/dtcexperiment_test.py @@ -17,10 +17,27 @@ import numpy as np import itertools + def test_DTCExperiment(): np.random.seed(5) - qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), (6, 6), - (6, 5), (7, 5), (8, 5), (8, 4), (8, 3), (7, 3), (6, 3)] + qubit_locations = [ + (3, 9), + (3, 8), + (3, 7), + (4, 7), + (4, 8), + (5, 8), + (5, 7), + (5, 6), + (6, 6), + (6, 5), + (7, 5), + (8, 5), + (8, 4), + (8, 3), + (7, 3), + (6, 3), + ] qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] num_qubits = len(qubits) @@ -31,36 +48,72 @@ def test_DTCExperiment(): thetas = np.zeros((instances, num_qubits - 1)) zetas = np.zeros((instances, num_qubits - 1)) chis = np.zeros((instances, num_qubits - 1)) - gammas = -np.random.uniform(0.5*np.pi, 1.5*np.pi, (instances, num_qubits - 1)) - phis = -2*gammas - args = ['qubits', 'g', 'initial_state', 'local_fields', - 'thetas', 'zetas', 'chis', 'gammas', 'phis'] + gammas = -np.random.uniform(0.5 * np.pi, 1.5 * np.pi, (instances, num_qubits - 1)) + phis = -2 * gammas + args = [ + "qubits", + "g", + "initial_state", + "local_fields", + "thetas", + "zetas", + "chis", + "gammas", + "phis", + ] default_resolvers = time_crystals.DTCExperiment().param_resolvers() for arg in args: kwargs = {} for name in args: kwargs[name] = None if name is arg else locals()[name] - dtcexperiment = time_crystals.DTCExperiment(disorder_instances=instances, **kwargs) + dtcexperiment = time_crystals.DTCExperiment( + disorder_instances=instances, **kwargs + ) param_resolvers = dtcexperiment.param_resolvers() + def test_comparison_experiments(): np.random.seed(5) - qubit_locations = [(3, 9), (3, 8), (3, 7), (4, 7), (4, 8), (5, 8), (5, 7), (5, 6), (6, 6), - (6, 5), (7, 5), (8, 5), (8, 4), (8, 3), (7, 3), (6, 3)] + qubit_locations = [ + (3, 9), + (3, 8), + (3, 7), + (4, 7), + (4, 8), + (5, 8), + (5, 7), + (5, 6), + (6, 6), + (6, 5), + (7, 5), + (8, 5), + (8, 4), + (8, 3), + (7, 3), + (6, 3), + ] qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] num_qubits = len(qubits) g_cases = [0.94, 0.6] instances = 36 - initial_states_cases = [np.random.choice(2, (instances, num_qubits)), - np.tile(np.random.choice(2, num_qubits), (instances,1))] - local_fields_cases = [np.random.uniform(-1.0, 1.0, (instances, num_qubits)), - np.tile(np.random.uniform(-1.0, 1.0, num_qubits), (instances,1))] - phis_cases = [np.random.uniform(np.pi, 3*np.pi, (instances, num_qubits - 1)), - np.full((instances, num_qubits - 1), 0.4)] - names = ['g_cases', 'initial_states_cases', 'local_fields_cases', 'phis_cases'] + initial_states_cases = [ + np.random.choice(2, (instances, num_qubits)), + np.tile(np.random.choice(2, num_qubits), (instances, 1)), + ] + local_fields_cases = [ + np.random.uniform(-1.0, 1.0, (instances, num_qubits)), + np.tile(np.random.uniform(-1.0, 1.0, num_qubits), (instances, 1)), + ] + phis_cases = [ + np.random.uniform(np.pi, 3 * np.pi, (instances, num_qubits - 1)), + np.full((instances, num_qubits - 1), 0.4), + ] + names = ["g_cases", "initial_states_cases", "local_fields_cases", "phis_cases"] args = [g_cases, initial_states_cases, local_fields_cases, phis_cases] - for cases in itertools.product(*zip([None]*4, args)): + for cases in itertools.product(*zip([None] * 4, args)): kwargs = dict(zip(names, cases)) - for experiment in time_crystals.comparison_experiments(qubits, instances, **kwargs): + for experiment in time_crystals.comparison_experiments( + qubits, instances, **kwargs + ): param_resolvers = experiment.param_resolvers() From edee86b883293c4849295c1ad1d412a3daa5ae88 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Mon, 24 Jan 2022 19:48:12 -0800 Subject: [PATCH 05/19] Improve docstring attribute descriptions --- recirq/time_crystals/dtcexperiment.py | 39 +++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/recirq/time_crystals/dtcexperiment.py b/recirq/time_crystals/dtcexperiment.py index 899882c9..d8bca8b8 100644 --- a/recirq/time_crystals/dtcexperiment.py +++ b/recirq/time_crystals/dtcexperiment.py @@ -26,16 +26,33 @@ class DTCExperiment: """Manage inputs to a DTC experiment, over some number of disorder instances Attributes: - qubits: a chain of connected qubits available for the circuit - disorder_instances: number of disorder instances averaged over - initial_states: initial state of the system used in circuit - g: thermalization constant used in circuit - local_fields: random noise used in circuit - thetas: theta parameters for FSim Gate used in circuit - zetas: zeta parameters for FSim Gate used in circuit - chis: chi parameters for FSim Gate used in circuit - phis: phi parameters for FSim Gate used in circuit - gammas: gamma parameters for FSim Gate used in circuit + qubits: a chain of connected qubits available for the circuit. + Defaults to 16 `cirq.Gridqubits` connected in a chain. + disorder_instances: number of disorder instances averaged over. + Defaults to 36. + g: thermalization constant used in circuit. + Defaults to 0.94. + initial_states: initial state of the system used in circuit. + Defaults to `np.ndarray` of shape (disorder_instances, num_qubits) of ints, + randomly selected from {0,1}. + local_fields: random noise used in circuit. + Defaults to `np.ndarray` of shape (disorder_instances, num_qubits) of floats, + randomly and uniformly selected from the range [-1.0, 1.0]. + thetas: theta parameters for FSim Gate used in circuit. + Defaults to `np.ndarray` of shape (disorder_instnaces, num_qubits - 1) of ints, + all set to zero. + zetas: zeta parameters for FSim Gate used in circuit. + Defaults to `np.ndarray` of shape (disorder_instnaces, num_qubits - 1) of ints, + all set to zero. + chis: chi parameters for FSim Gate used in circuit. + Defaults to `np.ndarray` of shape (disorder_instnaces, num_qubits - 1) of ints, + all set to zero. + phis: phi parameters for FSim Gate used in circuit. + Defaults to `np.ndarray` of shape (disorder_instances, num_qubits - 1) of floats, + randomly and uniformly selected from the range [-0.5*`np.pi`, -1.5*`np.pi`]. + gammas: gamma parameters for FSim Gate used in circuit. + Defaults to `np.ndarray` of shape (disorder_instances, num_qubits - 1) of floats, + computed as -2*gammas. """ @@ -156,7 +173,7 @@ def comparison_experiments( initial_states_cases: Optional[Sequence[np.ndarray]] = None, local_fields_cases: Optional[Sequence[np.ndarray]] = None, phis_cases: Optional[Sequence[np.ndarray]] = None, -) -> Generator[DTCExperiment, None, None]: +) -> Iterator[DTCExperiment]: """Yield DTCExperiments with parameters taken from the cartesian product of input parameters Args: Any number of (parameter, parameter_values) pairs From 924ec204f92f21066ddaa4aa6e44e12dfad8dfc3 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 28 Jan 2022 05:39:07 -0800 Subject: [PATCH 06/19] fix 2021 copyright to 2022 --- recirq/time_crystals/__init__.py | 2 +- recirq/time_crystals/dtcexperiment.py | 2 +- recirq/time_crystals/dtcexperiment_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/recirq/time_crystals/__init__.py b/recirq/time_crystals/__init__.py index 3c9c580e..bf8b2f44 100644 --- a/recirq/time_crystals/__init__.py +++ b/recirq/time_crystals/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 Google +# Copyright 2022 Google # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/recirq/time_crystals/dtcexperiment.py b/recirq/time_crystals/dtcexperiment.py index d8bca8b8..784a27e5 100644 --- a/recirq/time_crystals/dtcexperiment.py +++ b/recirq/time_crystals/dtcexperiment.py @@ -1,4 +1,4 @@ -# Copyright 2021 Google +# Copyright 2022 Google # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/recirq/time_crystals/dtcexperiment_test.py b/recirq/time_crystals/dtcexperiment_test.py index 12f6658a..877d0b30 100644 --- a/recirq/time_crystals/dtcexperiment_test.py +++ b/recirq/time_crystals/dtcexperiment_test.py @@ -1,4 +1,4 @@ -# Copyright 2021 Google +# Copyright 2022 Google # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 20304c18f86ba620f945b7d9a44900152b7b4dfe Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 28 Jan 2022 05:41:04 -0800 Subject: [PATCH 07/19] Add docstring newlines, add Iterator import --- recirq/time_crystals/dtcexperiment.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/recirq/time_crystals/dtcexperiment.py b/recirq/time_crystals/dtcexperiment.py index 784a27e5..172db27e 100644 --- a/recirq/time_crystals/dtcexperiment.py +++ b/recirq/time_crystals/dtcexperiment.py @@ -15,7 +15,7 @@ import cirq import itertools import numpy as np -from typing import Sequence, Optional, Generator +from typing import Sequence, Optional, Iterator import os EXPERIMENT_NAME = "time_crystals" @@ -139,8 +139,10 @@ def __init__( def param_resolvers(self) -> cirq.Zip: """return a sweep over disorder instances for the parameters of this experiment + Returns: `cirq.Zip` object with self.disorder_instances many `cirq.ParamResolver`s + """ # initialize the dict and add the first, non-qubit-dependent parameter, g @@ -175,10 +177,13 @@ def comparison_experiments( phis_cases: Optional[Sequence[np.ndarray]] = None, ) -> Iterator[DTCExperiment]: """Yield DTCExperiments with parameters taken from the cartesian product of input parameters + Args: Any number of (parameter, parameter_values) pairs + Yields: DTCTasks with parameters taken from self.options_dict + """ # take product over elements of options_dict, in the order of options_order From 40742753cb6321638aca5319d81620e229ab61f5 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 28 Jan 2022 06:25:49 -0800 Subject: [PATCH 08/19] Updated test cases --- recirq/time_crystals/dtcexperiment.py | 7 +- recirq/time_crystals/dtcexperiment_test.py | 92 +++++++++++++++------- 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/recirq/time_crystals/dtcexperiment.py b/recirq/time_crystals/dtcexperiment.py index 172db27e..e25ebcdd 100644 --- a/recirq/time_crystals/dtcexperiment.py +++ b/recirq/time_crystals/dtcexperiment.py @@ -187,13 +187,14 @@ def comparison_experiments( """ # take product over elements of options_dict, in the order of options_order - items = [ + argument_cases = [ ([x] if x is None else x) for x in [g_cases, initial_states_cases, local_fields_cases, phis_cases] ] - for components in itertools.product(*items): + argument_names = ["g", "initial_states", "local_fields", "phis"] + for arguments in itertools.product(*argument_cases): # prepare arguments for DTCExperiment - kwargs = dict(zip(["g", "initial_states", "local_fields", "phis"], components)) + kwargs = dict(zip(argument_names, arguments)) yield DTCExperiment( qubits=qubits, disorder_instances=disorder_instances, **kwargs ) diff --git a/recirq/time_crystals/dtcexperiment_test.py b/recirq/time_crystals/dtcexperiment_test.py index 877d0b30..3c223dc5 100644 --- a/recirq/time_crystals/dtcexperiment_test.py +++ b/recirq/time_crystals/dtcexperiment_test.py @@ -19,6 +19,10 @@ def test_DTCExperiment(): + """Test to check all combinations of defaults vs supplied inputs for + DTCExperiment, with the goal of checking all paths for crashes + + """ np.random.seed(5) qubit_locations = [ (3, 9), @@ -42,18 +46,33 @@ def test_DTCExperiment(): qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] num_qubits = len(qubits) g = 0.94 - instances = 36 - initial_state = np.random.choice(2, num_qubits) - local_fields = np.random.uniform(-1.0, 1.0, (instances, num_qubits)) - thetas = np.zeros((instances, num_qubits - 1)) - zetas = np.zeros((instances, num_qubits - 1)) - chis = np.zeros((instances, num_qubits - 1)) - gammas = -np.random.uniform(0.5 * np.pi, 1.5 * np.pi, (instances, num_qubits - 1)) + disorder_instances = 36 + initial_states = np.random.choice(2, (disorder_instances, num_qubits)) + local_fields = np.random.uniform(-1.0, 1.0, (disorder_instances, num_qubits)) + thetas = np.zeros((disorder_instances, num_qubits - 1)) + zetas = np.zeros((disorder_instances, num_qubits - 1)) + chis = np.zeros((disorder_instances, num_qubits - 1)) + gammas = -np.random.uniform( + 0.5 * np.pi, 1.5 * np.pi, (disorder_instances, num_qubits - 1) + ) phis = -2 * gammas - args = [ + arguments = [ + qubits, + g, + disorder_instances, + initial_states, + local_fields, + thetas, + zetas, + chis, + gammas, + phis, + ] + argument_names = [ "qubits", "g", - "initial_state", + "disorder_instances", + "initial_states", "local_fields", "thetas", "zetas", @@ -62,17 +81,23 @@ def test_DTCExperiment(): "phis", ] default_resolvers = time_crystals.DTCExperiment().param_resolvers() - for arg in args: - kwargs = {} - for name in args: - kwargs[name] = None if name is arg else locals()[name] - dtcexperiment = time_crystals.DTCExperiment( - disorder_instances=instances, **kwargs - ) - param_resolvers = dtcexperiment.param_resolvers() + for arguments_instance in itertools.product( + *zip(arguments, [None] * len(arguments)) + ): + kwargs = { + name: instance + for (name, instance) in zip(argument_names, arguments_instance) + if instance is not None + } + dtcexperiment = time_crystals.DTCExperiment(**kwargs) + param_resolvers = list(dtcexperiment.param_resolvers()) def test_comparison_experiments(): + """Test to check all combinations of defaults vs supplied inputs for + comparison_experiments, with the goal of checking all paths for crashes + + """ np.random.seed(5) qubit_locations = [ (3, 9), @@ -96,24 +121,31 @@ def test_comparison_experiments(): qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] num_qubits = len(qubits) g_cases = [0.94, 0.6] - instances = 36 + disorder_instances = 36 initial_states_cases = [ - np.random.choice(2, (instances, num_qubits)), - np.tile(np.random.choice(2, num_qubits), (instances, 1)), + np.random.choice(2, (disorder_instances, num_qubits)), + np.tile(np.random.choice(2, num_qubits), (disorder_instances, 1)), ] local_fields_cases = [ - np.random.uniform(-1.0, 1.0, (instances, num_qubits)), - np.tile(np.random.uniform(-1.0, 1.0, num_qubits), (instances, 1)), + np.random.uniform(-1.0, 1.0, (disorder_instances, num_qubits)), + np.tile(np.random.uniform(-1.0, 1.0, num_qubits), (disorder_instances, 1)), ] phis_cases = [ - np.random.uniform(np.pi, 3 * np.pi, (instances, num_qubits - 1)), - np.full((instances, num_qubits - 1), 0.4), + np.random.uniform(np.pi, 3 * np.pi, (disorder_instances, num_qubits - 1)), + np.full((disorder_instances, num_qubits - 1), 0.4), + ] + argument_names = [ + "g_cases", + "initial_states_cases", + "local_fields_cases", + "phis_cases", ] - names = ["g_cases", "initial_states_cases", "local_fields_cases", "phis_cases"] - args = [g_cases, initial_states_cases, local_fields_cases, phis_cases] - for cases in itertools.product(*zip([None] * 4, args)): - kwargs = dict(zip(names, cases)) + argument_cases = [g_cases, initial_states_cases, local_fields_cases, phis_cases] + for argument_case in itertools.product( + *zip([None] * len(argument_cases), argument_cases) + ): + kwargs = dict(zip(argument_names, argument_case)) for experiment in time_crystals.comparison_experiments( - qubits, instances, **kwargs + qubits, disorder_instances, **kwargs ): - param_resolvers = experiment.param_resolvers() + param_resolvers = list(experiment.param_resolvers()) From 5897f2606109abc19509575ee1a9141a0fb7b734 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Wed, 9 Mar 2022 13:23:56 -0800 Subject: [PATCH 09/19] Fix parameter descriptions --- recirq/time_crystals/dtcexperiment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recirq/time_crystals/dtcexperiment.py b/recirq/time_crystals/dtcexperiment.py index e25ebcdd..334e5f91 100644 --- a/recirq/time_crystals/dtcexperiment.py +++ b/recirq/time_crystals/dtcexperiment.py @@ -30,12 +30,12 @@ class DTCExperiment: Defaults to 16 `cirq.Gridqubits` connected in a chain. disorder_instances: number of disorder instances averaged over. Defaults to 36. - g: thermalization constant used in circuit. + g: control constant used in circuit. Defaults to 0.94. initial_states: initial state of the system used in circuit. Defaults to `np.ndarray` of shape (disorder_instances, num_qubits) of ints, randomly selected from {0,1}. - local_fields: random noise used in circuit. + local_fields: local fields that break integrability. Defaults to `np.ndarray` of shape (disorder_instances, num_qubits) of floats, randomly and uniformly selected from the range [-1.0, 1.0]. thetas: theta parameters for FSim Gate used in circuit. From 2873a73157a4c514d440ff7d1730f1d407dc5bcb Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Sun, 6 Feb 2022 17:14:59 -0800 Subject: [PATCH 10/19] Add DTC simulation utilities for for experiments --- recirq/time_crystals/__init__.py | 5 + recirq/time_crystals/dtcexperiment.py | 3 +- recirq/time_crystals/dtcsimulation.py | 353 +++++++++++++++++++++ recirq/time_crystals/dtcsimulation_test.py | 90 ++++++ 4 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 recirq/time_crystals/dtcsimulation.py create mode 100644 recirq/time_crystals/dtcsimulation_test.py diff --git a/recirq/time_crystals/__init__.py b/recirq/time_crystals/__init__.py index bf8b2f44..464c1c88 100644 --- a/recirq/time_crystals/__init__.py +++ b/recirq/time_crystals/__init__.py @@ -18,3 +18,8 @@ EXPERIMENT_NAME, DEFAULT_BASE_DIR, ) +from recirq.time_crystals.dtcsimulation import ( + run_comparison_experiment, + signal_ratio, + symbolic_dtc_circuit_list, +) diff --git a/recirq/time_crystals/dtcexperiment.py b/recirq/time_crystals/dtcexperiment.py index 334e5f91..16ae8a16 100644 --- a/recirq/time_crystals/dtcexperiment.py +++ b/recirq/time_crystals/dtcexperiment.py @@ -194,7 +194,8 @@ def comparison_experiments( argument_names = ["g", "initial_states", "local_fields", "phis"] for arguments in itertools.product(*argument_cases): # prepare arguments for DTCExperiment - kwargs = dict(zip(argument_names, arguments)) + named_args = zip(argument_names, arguments) + kwargs = {name: arg for (name, arg) in named_args if arg is not None} yield DTCExperiment( qubits=qubits, disorder_instances=disorder_instances, **kwargs ) diff --git a/recirq/time_crystals/dtcsimulation.py b/recirq/time_crystals/dtcsimulation.py new file mode 100644 index 00000000..4c53b069 --- /dev/null +++ b/recirq/time_crystals/dtcsimulation.py @@ -0,0 +1,353 @@ +# Copyright 2021 Google +# +# 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 typing import Sequence, Tuple, List + +import cirq +import functools +from recirq.time_crystals.dtcexperiment import DTCExperiment, comparison_experiments +import numpy as np +import sympy as sp + + +def symbolic_dtc_circuit_list( + qubits: Sequence[cirq.Qid], cycles: int +) -> List[cirq.Circuit]: + + """Create a list of symbolically parameterized dtc circuits, with increasing cycles + + Args: + qubits: ordered sequence of available qubits, which are connected in a chain + cycles: maximum number of cycles to generate up to + + Returns: + list of circuits with `0, 1, 2, ... cycles` many cycles + + """ + + num_qubits = len(qubits) + + # Symbol for g + g_value = sp.Symbol("g") + + # Symbols for random variance (h) and initial state, one per qubit + local_fields = sp.symbols(f"local_field_:{num_qubits}") + initial_state = sp.symbols(f"initial_state_:{num_qubits}") + + # Symbols used for PhasedFsimGate, one for every qubit pair in the chain + thetas = sp.symbols(f"theta_:{num_qubits - 1}") + zetas = sp.symbols(f"zeta_:{num_qubits - 1}") + chis = sp.symbols(f"chi_:{num_qubits - 1}") + gammas = sp.symbols(f"gamma_:{num_qubits - 1}") + phis = sp.symbols(f"phi_:{num_qubits - 1}") + + # Initial moment of Y gates, conditioned on initial state + initial_operations = cirq.Moment( + [cirq.Y(qubit) ** initial_state[index] for index, qubit in enumerate(qubits)] + ) + + # First component of U cycle, a moment of XZ gates. + sequence_operations = [] + for index, qubit in enumerate(qubits): + sequence_operations.append( + cirq.PhasedXZGate( + x_exponent=g_value, + axis_phase_exponent=0.0, + z_exponent=local_fields[index], + )(qubit) + ) + + # Initialize U cycle + u_cycle = [cirq.Moment(sequence_operations)] + + # Second and third components of U cycle, a chain of 2-qubit PhasedFSim gates + # The first component is all the 2-qubit PhasedFSim gates starting on even qubits + # The second component is the 2-qubit gates starting on odd qubits + operation_list = [] + other_operation_list = [] + previous_qubit = None + previous_index = None + for index, qubit in enumerate(qubits): + if previous_qubit is None: + previous_qubit = qubit + previous_index = index + continue + + # Add an fsim gate + coupling_gate = cirq.ops.PhasedFSimGate( + theta=thetas[previous_index], + zeta=zetas[previous_index], + chi=chis[previous_index], + gamma=gammas[previous_index], + phi=phis[previous_index], + ) + operation_list.append(coupling_gate.on(previous_qubit, qubit)) + + # Swap the operation lists, to avoid two-qubit gate overlap + previous_qubit = qubit + previous_index = index + temp_swap_list = operation_list + operation_list = other_operation_list + other_operation_list = temp_swap_list + + # Add the two components into the U cycle + u_cycle.append(cirq.Moment(operation_list)) + u_cycle.append(cirq.Moment(other_operation_list)) + + # Prepare a list of circuits, with n=0,1,2,3 ... cycles many cycles + circuit_list = [] + total_circuit = cirq.Circuit(initial_operations) + circuit_list.append(total_circuit.copy()) + for c in range(cycles): + for m in u_cycle: + total_circuit.append(m) + circuit_list.append(total_circuit.copy()) + + return circuit_list + + +def simulate_dtc_circuit_list( + circuit_list: Sequence[cirq.Circuit], + param_resolver: cirq.ParamResolver, + qubit_order: Sequence[cirq.Qid], +) -> np.ndarray: + """Simulate a dtc circuit list for a particular param_resolver + Utilizes on the fact that simulating the last circuit in the list also + simulates each previous circuit along the way + + Args: + circuit_list: DTC circuit list; each element is a circuit with + increasingly many cycles + param_resolver: `cirq.ParamResolver` to resolve symbolic parameters + qubit_order: ordered sequence of qubits connected in a chain + + Returns: + `np.ndarray` of shape (len(circuit_list), 2**number of qubits) representing + the probability of measuring each bit string, for each circuit in the list + + """ + + # prepare simulator + simulator = cirq.Simulator() + + # record lengths of circuits in list + circuit_positions = [len(c) - 1 for c in circuit_list] + + # only simulate one circuit, the last one + circuit = circuit_list[-1] + + # use simulate_moment_steps to recover all of the state vectors necessary, + # while only simulating the circuit list once + probabilities = [] + for k, step in enumerate( + simulator.simulate_moment_steps( + circuit=circuit, param_resolver=param_resolver, qubit_order=qubit_order + ) + ): + # add the state vector if the number of moments simulated so far is equal + # to the length of a circuit in the circuit_list + if k in circuit_positions: + probabilities.append(np.abs(step.state_vector()) ** 2) + + return np.asarray(probabilities) + + +def simulate_dtc_circuit_list_sweep( + circuit_list: Sequence[cirq.Circuit], + param_resolvers: Sequence[cirq.ParamResolver], + qubit_order: Sequence[cirq.Qid], +): + """Simulate a dtc circuit list over a sweep of param_resolvers + + Args: + circuit_list: DTC circuit list; each element is a circuit with + increasingly many cycles + param_resolvers: list of `cirq.ParamResolver`s to sweep over + qubit_order: ordered sequence of qubits connected in a chain + + Yields: + for each param_resolver, `np.ndarray`s of shape + (len(circuit_list), 2**number of qubits) representing the probability + of measuring each bit string, for each circuit in the list + + """ + + # iterate over param resolvers and simulate for each + for param_resolver in param_resolvers: + yield simulate_dtc_circuit_list(circuit_list, param_resolver, qubit_order) + + +def get_polarizations( + probabilities: np.ndarray, + num_qubits: int, + initial_states: np.ndarray = None, +) -> np.ndarray: + """Get polarizations from matrix of probabilities, possibly autocorrelated on + the initial state + + Args: + probabilities: `np.ndarray` of shape (:, cycles, 2**qubits) + representing probability to measure each bit string + num_qubits: the number of qubits in the circuit the probabilities + were generated from + initial_states: `np.ndarray` of shape (:, qubits) representing the initial + state for each dtc circuit list + + Returns: + `np.ndarray` of shape (:, cycles, qubits) that represents each + qubit's polarization + + """ + + # prepare list of polarizations for each qubit + polarizations = [] + for qubit_index in range(num_qubits): + # select all indices in range(2**num_qubits) for which the + # associated element of the statevector has qubit_index as zero + shift_by = num_qubits - qubit_index - 1 + state_vector_indices = [ + i for i in range(2**num_qubits) if not (i >> shift_by) % 2 + ] + + # sum over all probabilities for qubit states for which qubit_index is zero, + # and rescale them to [-1,1] + polarization = ( + 2.0 + * np.sum( + probabilities.take(indices=state_vector_indices, axis=-1), + axis=-1, + ) + - 1.0 + ) + polarizations.append(polarization) + + # turn polarizations list into an array, + # and move the new, leftmost axis for qubits to the end + polarizations = np.moveaxis(np.asarray(polarizations), 0, -1) + + # flip polarizations according to the associated initial_state, if provided + # this means that the polarization of a qubit is relative to it's initial state + if initial_states is not None: + initial_states = 1 - 2.0 * initial_states + polarizations = initial_states * polarizations + + return polarizations + + +def signal_ratio(zeta_1: np.ndarray, zeta_2: np.ndarray): + """Calculate signal ratio between two signals + + Args: + zeta_1: signal (`np.ndarray` to represent polarization over time) + zeta 2: signal (`np.ndarray` to represent polarization over time) + + Returns: + computed ratio signal of zeta_1 and zeta_2 (`np.ndarray`) + to represent polarization over time) + + """ + + return np.abs(zeta_1 - zeta_2) / (np.abs(zeta_1) + np.abs(zeta_2)) + + +def simulate_for_polarizations( + dtcexperiment: DTCExperiment, + circuit_list: Sequence[cirq.Circuit], + autocorrelate: bool = True, + take_abs: bool = False, +): + """Simulate and get polarizations for a single DTCExperiment and circuit list + + Args: + dtcexperiment: DTCExperiment noting the parameters to simulate over some + number of disorder instances + circuit_list: symbolic dtc circuit list + autocorrelate: whether or not to autocorrelate the polarizations with their + respective initial states + take_abs: whether or not to take the absolute value of the polarizations + + Returns: + simulated polarizations (np.ndarray of shape (num_cycles, num_qubits)) from + the experiment, averaged over disorder instances + + """ + + # create param resolver sweep + param_resolvers = dtcexperiment.param_resolvers() + + # prepare simulation generator + probabilities_generator = simulate_dtc_circuit_list_sweep( + circuit_list, param_resolvers, dtcexperiment.qubits + ) + + # map get_polarizations over probabilities_generator + polarizations_generator = map( + lambda probabilities, initial_state: get_polarizations( + probabilities, + num_qubits=len(dtcexperiment.qubits), + initial_states=(initial_state if autocorrelate else None), + ), + probabilities_generator, + dtcexperiment.initial_states, + ) + + # take sum of (absolute value of) polarizations over different disorder instances + polarization_sum = functools.reduce( + lambda x, y: x + (np.abs(y) if take_abs else y), + polarizations_generator, + np.zeros((len(circuit_list), len(dtcexperiment.qubits))), + ) + + # get average over disorder instances + disorder_averaged_polarizations = ( + polarization_sum / dtcexperiment.disorder_instances + ) + + return disorder_averaged_polarizations + + +def run_comparison_experiment( + qubits: Sequence[cirq.Qid], + cycles: int, + disorder_instances: int, + autocorrelate: bool = True, + take_abs: bool = False, + **kwargs, +): + """Run comparison experiment + + Args: + qubits: ordered sequence of available qubits, which are connected in a chain + cycles: maximum number of cycles to generate up to + autocorrelate: whether or not to autocorrelate the polarizations with their + respective initial states + take_abs: whether or not to take the absolute value of the polarizations + kwargs: lists of non-default argument configurations to pass through + to `dtcexperiment.comparison_experiments` + + Yields: + disorder averaged polarizations, ordered by + `dtcexperiment.comparison_experiments`, with all other parameters default + + """ + circuit_list = symbolic_dtc_circuit_list(qubits, cycles) + for dtcexperiment in comparison_experiments( + qubits=qubits, disorder_instances=disorder_instances, **kwargs + ): + yield simulate_for_polarizations( + dtcexperiment=dtcexperiment, + circuit_list=circuit_list, + autocorrelate=autocorrelate, + take_abs=take_abs, + ) diff --git a/recirq/time_crystals/dtcsimulation_test.py b/recirq/time_crystals/dtcsimulation_test.py new file mode 100644 index 00000000..e240620b --- /dev/null +++ b/recirq/time_crystals/dtcsimulation_test.py @@ -0,0 +1,90 @@ +import recirq.time_crystals as time_crystals +import numpy as np +import itertools +from typing import List +import cirq + + +def test_run_comparison_experiment(): + """Test to check all combinations of defaults vs supplied inputs for + run_comparison_experiments, with the goal of checking all paths for crashes + + """ + + print("testing run_comparison_rexperiment") + np.random.seed(5) + qubit_locations = [ + (3, 9), + (3, 8), + (3, 7), + (4, 7), + ] + + qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] + num_qubits = len(qubits) + cycles = 5 + g_cases = [0.94, 0.6] + disorder_instances = 5 + initial_states_cases = [ + np.random.choice(2, (disorder_instances, num_qubits)), + np.tile(np.random.choice(2, num_qubits), (disorder_instances, 1)), + ] + local_fields_cases = [ + np.random.uniform(-1.0, 1.0, (disorder_instances, num_qubits)), + np.tile(np.random.uniform(-1.0, 1.0, num_qubits), (disorder_instances, 1)), + ] + phis_cases = [ + np.random.uniform(np.pi, 3 * np.pi, (disorder_instances, num_qubits - 1)), + np.full((disorder_instances, num_qubits - 1), 0.4), + ] + argument_names = [ + "g_cases", + "initial_states_cases", + "local_fields_cases", + "phis_cases", + ] + argument_cases = [g_cases, initial_states_cases, local_fields_cases, phis_cases] + paired_with_none = zip(argument_cases, [None] * len(argument_cases)) + + for autocorrelate, take_abs in itertools.product([True, False], repeat=2): + for argument_case in itertools.product(*paired_with_none): + named_arguments = zip(argument_names, argument_case) + kwargs = { + name: args for (name, args) in named_arguments if args is not None + } + for polarizations in time_crystals.run_comparison_experiment( + qubits, cycles, disorder_instances, autocorrelate, take_abs, **kwargs + ): + pass + print("testing run_comparison_rexperiment complete") + + +def test_signal_ratio(): + """Test signal_ratio function with random `np.ndarrays`""" + + print("testing signal_ratio") + np.random.seed(5) + cycles = 100 + num_qubits = 16 + zeta_1 = np.random.uniform(-1.0, 1.0, (cycles, num_qubits)) + zeta_2 = np.random.uniform(-1.0, 1.0, (cycles, num_qubits)) + res = time_crystals.signal_ratio(zeta_1, zeta_2) + print("testing signal_ratio complete") + + +def test_symbolic_dtc_circuit_list(): + """Test symbolic_dtc_circuit_list function for select qubits and cycles""" + + print("testing symbolic_dtc_circuit_list") + qubit_locations = [ + (3, 9), + (3, 8), + (3, 7), + (4, 7), + ] + + qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] + num_qubits = len(qubits) + cycles = 5 + circuit_list = time_crystals.symbolic_dtc_circuit_list(qubits, cycles) + print("testing symbolic_dtc_circuit_list complete") From 92340926c72997fbdd101451226fbaf36bb9b0f8 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Sun, 6 Feb 2022 17:27:51 -0800 Subject: [PATCH 11/19] Copyright addition, add type hints --- recirq/time_crystals/dtcsimulation.py | 12 ++++++------ recirq/time_crystals/dtcsimulation_test.py | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/recirq/time_crystals/dtcsimulation.py b/recirq/time_crystals/dtcsimulation.py index 4c53b069..db00a311 100644 --- a/recirq/time_crystals/dtcsimulation.py +++ b/recirq/time_crystals/dtcsimulation.py @@ -1,4 +1,4 @@ -# Copyright 2021 Google +# Copyright 2022 Google # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Sequence, Tuple, List +from typing import Sequence, Tuple, List, Iterator import cirq import functools @@ -167,7 +167,7 @@ def simulate_dtc_circuit_list_sweep( circuit_list: Sequence[cirq.Circuit], param_resolvers: Sequence[cirq.ParamResolver], qubit_order: Sequence[cirq.Qid], -): +) -> Iterator[np.ndarray]: """Simulate a dtc circuit list over a sweep of param_resolvers Args: @@ -245,7 +245,7 @@ def get_polarizations( return polarizations -def signal_ratio(zeta_1: np.ndarray, zeta_2: np.ndarray): +def signal_ratio(zeta_1: np.ndarray, zeta_2: np.ndarray) -> np.ndarray: """Calculate signal ratio between two signals Args: @@ -266,7 +266,7 @@ def simulate_for_polarizations( circuit_list: Sequence[cirq.Circuit], autocorrelate: bool = True, take_abs: bool = False, -): +) -> np.ndarray: """Simulate and get polarizations for a single DTCExperiment and circuit list Args: @@ -324,7 +324,7 @@ def run_comparison_experiment( autocorrelate: bool = True, take_abs: bool = False, **kwargs, -): +) -> Iterator[np.ndarray]: """Run comparison experiment Args: diff --git a/recirq/time_crystals/dtcsimulation_test.py b/recirq/time_crystals/dtcsimulation_test.py index e240620b..86dc4dfc 100644 --- a/recirq/time_crystals/dtcsimulation_test.py +++ b/recirq/time_crystals/dtcsimulation_test.py @@ -1,3 +1,17 @@ +# Copyright 2022 Google +# +# 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 recirq.time_crystals as time_crystals import numpy as np import itertools From 67d9b313e015745a5dcabbb572061ad56055e08e Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Thu, 21 Apr 2022 21:46:50 -0700 Subject: [PATCH 12/19] Fix imports broken in merge --- recirq/time_crystals/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/recirq/time_crystals/__init__.py b/recirq/time_crystals/__init__.py index bf8b2f44..dd6d91b1 100644 --- a/recirq/time_crystals/__init__.py +++ b/recirq/time_crystals/__init__.py @@ -18,3 +18,9 @@ EXPERIMENT_NAME, DEFAULT_BASE_DIR, ) + +from recirq.time_crystals.dtcsimulation import ( + run_comparison_experiment, + signal_ratio, + symbolic_dtc_circuit_list, +) From 64c0f7acb6b2436213027038da4405e0caf6db59 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Sat, 7 May 2022 10:50:46 -0700 Subject: [PATCH 13/19] Rename dtcsimulation -> dtc_simulation --- recirq/time_crystals/__init__.py | 2 +- recirq/time_crystals/{dtcsimulation.py => dtc_simulation.py} | 0 .../{dtcsimulation_test.py => dtc_simulation_test.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename recirq/time_crystals/{dtcsimulation.py => dtc_simulation.py} (100%) rename recirq/time_crystals/{dtcsimulation_test.py => dtc_simulation_test.py} (100%) diff --git a/recirq/time_crystals/__init__.py b/recirq/time_crystals/__init__.py index dd6d91b1..3327620d 100644 --- a/recirq/time_crystals/__init__.py +++ b/recirq/time_crystals/__init__.py @@ -19,7 +19,7 @@ DEFAULT_BASE_DIR, ) -from recirq.time_crystals.dtcsimulation import ( +from recirq.time_crystals.dtc_simulation import ( run_comparison_experiment, signal_ratio, symbolic_dtc_circuit_list, diff --git a/recirq/time_crystals/dtcsimulation.py b/recirq/time_crystals/dtc_simulation.py similarity index 100% rename from recirq/time_crystals/dtcsimulation.py rename to recirq/time_crystals/dtc_simulation.py diff --git a/recirq/time_crystals/dtcsimulation_test.py b/recirq/time_crystals/dtc_simulation_test.py similarity index 100% rename from recirq/time_crystals/dtcsimulation_test.py rename to recirq/time_crystals/dtc_simulation_test.py From f51bcf85526f42df235220b3caf6d43329eb2e5d Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Sat, 7 May 2022 19:24:15 -0700 Subject: [PATCH 14/19] Feedback Updates --- recirq/time_crystals/dtc_simulation.py | 69 +++++++++------------ recirq/time_crystals/dtc_simulation_test.py | 14 ++--- 2 files changed, 34 insertions(+), 49 deletions(-) diff --git a/recirq/time_crystals/dtc_simulation.py b/recirq/time_crystals/dtc_simulation.py index db00a311..e5f32831 100644 --- a/recirq/time_crystals/dtc_simulation.py +++ b/recirq/time_crystals/dtc_simulation.py @@ -14,17 +14,16 @@ from typing import Sequence, Tuple, List, Iterator -import cirq import functools -from recirq.time_crystals.dtcexperiment import DTCExperiment, comparison_experiments import numpy as np import sympy as sp +import cirq +from recirq.time_crystals.dtcexperiment import DTCExperiment, comparison_experiments def symbolic_dtc_circuit_list( qubits: Sequence[cirq.Qid], cycles: int ) -> List[cirq.Circuit]: - """Create a list of symbolically parameterized dtc circuits, with increasing cycles Args: @@ -35,7 +34,6 @@ def symbolic_dtc_circuit_list( list of circuits with `0, 1, 2, ... cycles` many cycles """ - num_qubits = len(qubits) # Symbol for g @@ -74,44 +72,35 @@ def symbolic_dtc_circuit_list( # Second and third components of U cycle, a chain of 2-qubit PhasedFSim gates # The first component is all the 2-qubit PhasedFSim gates starting on even qubits # The second component is the 2-qubit gates starting on odd qubits - operation_list = [] - other_operation_list = [] - previous_qubit = None - previous_index = None - for index, qubit in enumerate(qubits): - if previous_qubit is None: - previous_qubit = qubit - previous_index = index - continue - + current_moment = [] + other_moment = [] + for index, (qubit, next_qubit) in enumerate(zip(qubits, qubits[1:])): # Add an fsim gate coupling_gate = cirq.ops.PhasedFSimGate( - theta=thetas[previous_index], - zeta=zetas[previous_index], - chi=chis[previous_index], - gamma=gammas[previous_index], - phi=phis[previous_index], + theta=thetas[index], + zeta=zetas[index], + chi=chis[index], + gamma=gammas[index], + phi=phis[index], ) - operation_list.append(coupling_gate.on(previous_qubit, qubit)) + current_moment.append(coupling_gate.on(qubit, next_qubit)) - # Swap the operation lists, to avoid two-qubit gate overlap - previous_qubit = qubit - previous_index = index - temp_swap_list = operation_list - operation_list = other_operation_list - other_operation_list = temp_swap_list + # Apply to the other moment in the next iteration + swap_moment = current_moment + current_moment = other_moment + other_moment = swap_moment # Add the two components into the U cycle - u_cycle.append(cirq.Moment(operation_list)) - u_cycle.append(cirq.Moment(other_operation_list)) + u_cycle.append(cirq.Moment(current_moment)) + u_cycle.append(cirq.Moment(other_moment)) # Prepare a list of circuits, with n=0,1,2,3 ... cycles many cycles circuit_list = [] total_circuit = cirq.Circuit(initial_operations) circuit_list.append(total_circuit.copy()) - for c in range(cycles): - for m in u_cycle: - total_circuit.append(m) + for _ in range(cycles): + for moment in u_cycle: + total_circuit.append(moment) circuit_list.append(total_circuit.copy()) return circuit_list @@ -123,7 +112,8 @@ def simulate_dtc_circuit_list( qubit_order: Sequence[cirq.Qid], ) -> np.ndarray: """Simulate a dtc circuit list for a particular param_resolver - Utilizes on the fact that simulating the last circuit in the list also + + Utilizes the fact that simulating the last circuit in the list also simulates each previous circuit along the way Args: @@ -137,12 +127,14 @@ def simulate_dtc_circuit_list( the probability of measuring each bit string, for each circuit in the list """ - # prepare simulator simulator = cirq.Simulator() # record lengths of circuits in list - circuit_positions = [len(c) - 1 for c in circuit_list] + assert all( + len(x) < len(y) for x, y in zip(circuit_list, circuit_list[1:]) + ), "circuits in circuit_list are not in increasing order of size" + circuit_positions = {len(c) - 1 for c in circuit_list} # only simulate one circuit, the last one circuit = circuit_list[-1] @@ -182,7 +174,6 @@ def simulate_dtc_circuit_list_sweep( of measuring each bit string, for each circuit in the list """ - # iterate over param resolvers and simulate for each for param_resolver in param_resolvers: yield simulate_dtc_circuit_list(circuit_list, param_resolver, qubit_order) @@ -194,7 +185,10 @@ def get_polarizations( initial_states: np.ndarray = None, ) -> np.ndarray: """Get polarizations from matrix of probabilities, possibly autocorrelated on - the initial state + the initial state. + + A polarization is the marginal probability for a qubit to measure zero or one, + over all possible basis states, scaled to the range [-1. 1]. Args: probabilities: `np.ndarray` of shape (:, cycles, 2**qubits) @@ -209,7 +203,6 @@ def get_polarizations( qubit's polarization """ - # prepare list of polarizations for each qubit polarizations = [] for qubit_index in range(num_qubits): @@ -257,7 +250,6 @@ def signal_ratio(zeta_1: np.ndarray, zeta_2: np.ndarray) -> np.ndarray: to represent polarization over time) """ - return np.abs(zeta_1 - zeta_2) / (np.abs(zeta_1) + np.abs(zeta_2)) @@ -282,7 +274,6 @@ def simulate_for_polarizations( the experiment, averaged over disorder instances """ - # create param resolver sweep param_resolvers = dtcexperiment.param_resolvers() diff --git a/recirq/time_crystals/dtc_simulation_test.py b/recirq/time_crystals/dtc_simulation_test.py index 86dc4dfc..9a245163 100644 --- a/recirq/time_crystals/dtc_simulation_test.py +++ b/recirq/time_crystals/dtc_simulation_test.py @@ -24,8 +24,6 @@ def test_run_comparison_experiment(): run_comparison_experiments, with the goal of checking all paths for crashes """ - - print("testing run_comparison_rexperiment") np.random.seed(5) qubit_locations = [ (3, 9), @@ -69,27 +67,22 @@ def test_run_comparison_experiment(): for polarizations in time_crystals.run_comparison_experiment( qubits, cycles, disorder_instances, autocorrelate, take_abs, **kwargs ): - pass - print("testing run_comparison_rexperiment complete") + assert polarizations.shape == (cycles + 1, num_qubits) + assert np.all(-1 <= polarizations) and np.all(polarizations <= 1) def test_signal_ratio(): """Test signal_ratio function with random `np.ndarrays`""" - - print("testing signal_ratio") np.random.seed(5) cycles = 100 num_qubits = 16 zeta_1 = np.random.uniform(-1.0, 1.0, (cycles, num_qubits)) zeta_2 = np.random.uniform(-1.0, 1.0, (cycles, num_qubits)) res = time_crystals.signal_ratio(zeta_1, zeta_2) - print("testing signal_ratio complete") def test_symbolic_dtc_circuit_list(): """Test symbolic_dtc_circuit_list function for select qubits and cycles""" - - print("testing symbolic_dtc_circuit_list") qubit_locations = [ (3, 9), (3, 8), @@ -101,4 +94,5 @@ def test_symbolic_dtc_circuit_list(): num_qubits = len(qubits) cycles = 5 circuit_list = time_crystals.symbolic_dtc_circuit_list(qubits, cycles) - print("testing symbolic_dtc_circuit_list complete") + for index, circuit in enumerate(circuit_list): + assert len(circuit) == 3*index + 1 From 169a32c157771f2a984321f4a9943aeaa00d0d53 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Sat, 7 May 2022 19:30:18 -0700 Subject: [PATCH 15/19] re-run formatter --- recirq/time_crystals/dtc_simulation.py | 1 + recirq/time_crystals/dtc_simulation_test.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/recirq/time_crystals/dtc_simulation.py b/recirq/time_crystals/dtc_simulation.py index e5f32831..b6dabafc 100644 --- a/recirq/time_crystals/dtc_simulation.py +++ b/recirq/time_crystals/dtc_simulation.py @@ -21,6 +21,7 @@ import cirq from recirq.time_crystals.dtcexperiment import DTCExperiment, comparison_experiments + def symbolic_dtc_circuit_list( qubits: Sequence[cirq.Qid], cycles: int ) -> List[cirq.Circuit]: diff --git a/recirq/time_crystals/dtc_simulation_test.py b/recirq/time_crystals/dtc_simulation_test.py index 9a245163..a1f0d1ed 100644 --- a/recirq/time_crystals/dtc_simulation_test.py +++ b/recirq/time_crystals/dtc_simulation_test.py @@ -95,4 +95,4 @@ def test_symbolic_dtc_circuit_list(): cycles = 5 circuit_list = time_crystals.symbolic_dtc_circuit_list(qubits, cycles) for index, circuit in enumerate(circuit_list): - assert len(circuit) == 3*index + 1 + assert len(circuit) == 3 * index + 1 From c9c2bd3b2698e432853b982ed6c5c85959bd7d78 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Thu, 12 May 2022 22:58:51 -0700 Subject: [PATCH 16/19] Feedback updates v1 --- recirq/time_crystals/dtc_simulation.py | 42 ++++++++++------ recirq/time_crystals/dtc_simulation_test.py | 56 ++++++++++----------- 2 files changed, 55 insertions(+), 43 deletions(-) diff --git a/recirq/time_crystals/dtc_simulation.py b/recirq/time_crystals/dtc_simulation.py index b6dabafc..e758d7e6 100644 --- a/recirq/time_crystals/dtc_simulation.py +++ b/recirq/time_crystals/dtc_simulation.py @@ -73,8 +73,8 @@ def symbolic_dtc_circuit_list( # Second and third components of U cycle, a chain of 2-qubit PhasedFSim gates # The first component is all the 2-qubit PhasedFSim gates starting on even qubits # The second component is the 2-qubit gates starting on odd qubits - current_moment = [] - other_moment = [] + even_qubit_moment = [] + odd_qubit_moment = [] for index, (qubit, next_qubit) in enumerate(zip(qubits, qubits[1:])): # Add an fsim gate coupling_gate = cirq.ops.PhasedFSimGate( @@ -84,16 +84,15 @@ def symbolic_dtc_circuit_list( gamma=gammas[index], phi=phis[index], ) - current_moment.append(coupling_gate.on(qubit, next_qubit)) - # Apply to the other moment in the next iteration - swap_moment = current_moment - current_moment = other_moment - other_moment = swap_moment + if index % 2: + even_qubit_moment.append(coupling_gate.on(qubit, next_qubit)) + else: + odd_qubit_moment.append(coupling_gate.on(qubit, next_qubit)) # Add the two components into the U cycle - u_cycle.append(cirq.Moment(current_moment)) - u_cycle.append(cirq.Moment(other_moment)) + u_cycle.append(cirq.Moment(even_qubit_moment)) + u_cycle.append(cirq.Moment(odd_qubit_moment)) # Prepare a list of circuits, with n=0,1,2,3 ... cycles many cycles circuit_list = [] @@ -111,6 +110,7 @@ def simulate_dtc_circuit_list( circuit_list: Sequence[cirq.Circuit], param_resolver: cirq.ParamResolver, qubit_order: Sequence[cirq.Qid], + simulator: "cirq.SimulatesIntermediateState" = None, ) -> np.ndarray: """Simulate a dtc circuit list for a particular param_resolver @@ -122,6 +122,8 @@ def simulate_dtc_circuit_list( increasingly many cycles param_resolver: `cirq.ParamResolver` to resolve symbolic parameters qubit_order: ordered sequence of qubits connected in a chain + simulator: Optional simulator object which must + support the `simulate_moment_steps` function Returns: `np.ndarray` of shape (len(circuit_list), 2**number of qubits) representing @@ -129,12 +131,12 @@ def simulate_dtc_circuit_list( """ # prepare simulator - simulator = cirq.Simulator() + if simulator is None: + simulator = cirq.Simulator() # record lengths of circuits in list - assert all( - len(x) < len(y) for x, y in zip(circuit_list, circuit_list[1:]) - ), "circuits in circuit_list are not in increasing order of size" + if not all(len(x) < len(y) for x, y in zip(circuit_list, circuit_list[1:])): + raise ValueError("circuits in circuit_list are not in increasing order of size") circuit_positions = {len(c) - 1 for c in circuit_list} # only simulate one circuit, the last one @@ -242,12 +244,15 @@ def get_polarizations( def signal_ratio(zeta_1: np.ndarray, zeta_2: np.ndarray) -> np.ndarray: """Calculate signal ratio between two signals + Signal ratio measures how different two signals are, + proportional to how large they are. + Args: zeta_1: signal (`np.ndarray` to represent polarization over time) zeta 2: signal (`np.ndarray` to represent polarization over time) Returns: - computed ratio signal of zeta_1 and zeta_2 (`np.ndarray`) + computed ratio of the signals zeta_1 and zeta_2 (`np.ndarray`) to represent polarization over time) """ @@ -317,7 +322,14 @@ def run_comparison_experiment( take_abs: bool = False, **kwargs, ) -> Iterator[np.ndarray]: - """Run comparison experiment + """Run multiple DTC experiments for qubit polarizations over different parameters. + + This uses the default parameter options noted in + `dtcexperiment.comparison_experiments` for any parameter not supplied in + kwargs. A DTC experiment is then created and simulated for each possible + parameter combination before qubit polarizations by DTC cycle are + computed and yielded. Each yield is an `np.ndarray` of shape (qubits, cycles) + for a specific combination of parameters. Args: qubits: ordered sequence of available qubits, which are connected in a chain diff --git a/recirq/time_crystals/dtc_simulation_test.py b/recirq/time_crystals/dtc_simulation_test.py index a1f0d1ed..7faf4fff 100644 --- a/recirq/time_crystals/dtc_simulation_test.py +++ b/recirq/time_crystals/dtc_simulation_test.py @@ -18,6 +18,24 @@ from typing import List import cirq +qubit_locations = [ + (3, 9), + (3, 8), + (3, 7), + (4, 7), +] + +QUBITS = [cirq.GridQubit(*idx) for idx in qubit_locations] +NUM_QUBITS = len(QUBITS) + + +def polarizations_predicate(polarizations, cycles, num_qubits): + return ( + polarizations.shape == (cycles + 1, NUM_QUBITS) + and np.all(-1 <= polarizations) + and np.all(polarizations <= 1) + ) + def test_run_comparison_experiment(): """Test to check all combinations of defaults vs supplied inputs for @@ -25,29 +43,20 @@ def test_run_comparison_experiment(): """ np.random.seed(5) - qubit_locations = [ - (3, 9), - (3, 8), - (3, 7), - (4, 7), - ] - - qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] - num_qubits = len(qubits) cycles = 5 g_cases = [0.94, 0.6] disorder_instances = 5 initial_states_cases = [ - np.random.choice(2, (disorder_instances, num_qubits)), - np.tile(np.random.choice(2, num_qubits), (disorder_instances, 1)), + np.random.choice(2, (disorder_instances, NUM_QUBITS)), + np.tile(np.random.choice(2, NUM_QUBITS), (disorder_instances, 1)), ] local_fields_cases = [ - np.random.uniform(-1.0, 1.0, (disorder_instances, num_qubits)), - np.tile(np.random.uniform(-1.0, 1.0, num_qubits), (disorder_instances, 1)), + np.random.uniform(-1.0, 1.0, (disorder_instances, NUM_QUBITS)), + np.tile(np.random.uniform(-1.0, 1.0, NUM_QUBITS), (disorder_instances, 1)), ] phis_cases = [ - np.random.uniform(np.pi, 3 * np.pi, (disorder_instances, num_qubits - 1)), - np.full((disorder_instances, num_qubits - 1), 0.4), + np.random.uniform(np.pi, 3 * np.pi, (disorder_instances, NUM_QUBITS - 1)), + np.full((disorder_instances, NUM_QUBITS - 1), 0.4), ] argument_names = [ "g_cases", @@ -65,10 +74,9 @@ def test_run_comparison_experiment(): name: args for (name, args) in named_arguments if args is not None } for polarizations in time_crystals.run_comparison_experiment( - qubits, cycles, disorder_instances, autocorrelate, take_abs, **kwargs + QUBITS, cycles, disorder_instances, autocorrelate, take_abs, **kwargs ): - assert polarizations.shape == (cycles + 1, num_qubits) - assert np.all(-1 <= polarizations) and np.all(polarizations <= 1) + assert polarizations_predicate(polarizations, cycles, NUM_QUBITS) def test_signal_ratio(): @@ -79,20 +87,12 @@ def test_signal_ratio(): zeta_1 = np.random.uniform(-1.0, 1.0, (cycles, num_qubits)) zeta_2 = np.random.uniform(-1.0, 1.0, (cycles, num_qubits)) res = time_crystals.signal_ratio(zeta_1, zeta_2) + assert np.all(res >= 0) and np.all(res <= 1) def test_symbolic_dtc_circuit_list(): """Test symbolic_dtc_circuit_list function for select qubits and cycles""" - qubit_locations = [ - (3, 9), - (3, 8), - (3, 7), - (4, 7), - ] - - qubits = [cirq.GridQubit(*idx) for idx in qubit_locations] - num_qubits = len(qubits) cycles = 5 - circuit_list = time_crystals.symbolic_dtc_circuit_list(qubits, cycles) + circuit_list = time_crystals.symbolic_dtc_circuit_list(QUBITS, cycles) for index, circuit in enumerate(circuit_list): assert len(circuit) == 3 * index + 1 From 21f5fb353ac61756f10252b8b9d2dd00fa3273cf Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 13 May 2022 03:56:37 -0700 Subject: [PATCH 17/19] Add more unit level tests --- recirq/time_crystals/dtc_simulation_test.py | 89 +++++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/recirq/time_crystals/dtc_simulation_test.py b/recirq/time_crystals/dtc_simulation_test.py index 7faf4fff..ad5f3bd7 100644 --- a/recirq/time_crystals/dtc_simulation_test.py +++ b/recirq/time_crystals/dtc_simulation_test.py @@ -12,11 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import recirq.time_crystals as time_crystals +from typing import List + import numpy as np import itertools -from typing import List +import pytest + import cirq +import recirq.time_crystals as time_crystals qubit_locations = [ (3, 9), @@ -29,14 +32,90 @@ NUM_QUBITS = len(QUBITS) -def polarizations_predicate(polarizations, cycles, num_qubits): +def probabilities_predicate(probabilities, shape): + return ( + probabilities.shape == shape + and np.all(0 <= probabilities) + and np.all(probabilities <= 1) + ) + + +def polarizations_predicate(polarizations, shape): return ( - polarizations.shape == (cycles + 1, NUM_QUBITS) + polarizations.shape == shape and np.all(-1 <= polarizations) and np.all(polarizations <= 1) ) +def test_simulate_dtc_circuit_list_sweep(): + cycles = 5 + circuit_list = time_crystals.symbolic_dtc_circuit_list(QUBITS, cycles) + param_resolvers = time_crystals.DTCExperiment(qubits=QUBITS).param_resolvers() + qubit_order = QUBITS + for probabilities in time_crystals.dtc_simulation.simulate_dtc_circuit_list_sweep( + circuit_list, param_resolvers, qubit_order + ): + assert probabilities_predicate(probabilities, (cycles + 1, 2**NUM_QUBITS)) + + with pytest.raises( + ValueError, match="circuits in circuit_list are not in increasing order of size" + ): + time_crystals.dtc_simulation.simulate_dtc_circuit_list( + list(reversed(circuit_list)), param_resolvers, qubit_order + ) + + +def test_simulate_dtc_circuit_list(): + cycles = 5 + circuit_list = time_crystals.symbolic_dtc_circuit_list(QUBITS, cycles) + param_resolver = next( + iter(time_crystals.DTCExperiment(qubits=QUBITS).param_resolvers()) + ) + qubit_order = QUBITS + probabilities = time_crystals.dtc_simulation.simulate_dtc_circuit_list( + circuit_list, param_resolver, qubit_order + ) + assert probabilities_predicate(probabilities, (cycles + 1, 2**NUM_QUBITS)) + with pytest.raises( + ValueError, match="circuits in circuit_list are not in increasing order of size" + ): + time_crystals.dtc_simulation.simulate_dtc_circuit_list( + list(reversed(circuit_list)), param_resolver, qubit_order + ) + + +def test_get_polarizations(): + cycles = 5 + np.random.seed(5) + probabilities = np.random.uniform(0, 1, (cycles, 2**NUM_QUBITS)) + probabilities = probabilities / probabilities.sum(axis=1, keepdims=True) + initial_states = np.random.choice(2, NUM_QUBITS) + assert polarizations_predicate( + time_crystals.dtc_simulation.get_polarizations(probabilities, NUM_QUBITS), + (cycles, NUM_QUBITS), + ) + assert polarizations_predicate( + time_crystals.dtc_simulation.get_polarizations( + probabilities, NUM_QUBITS, initial_states + ), + (cycles, NUM_QUBITS), + ) + + +def test_simulate_for_polarizations(): + cycles = 5 + circuit_list = time_crystals.symbolic_dtc_circuit_list(qubits=QUBITS, cycles=cycles) + dtcexperiment = time_crystals.DTCExperiment(qubits=QUBITS) + for autocorrelate, take_abs in itertools.product([True, False], repeat=2): + assert polarizations_predicate(time_crystals.dtc_simulation.simulate_for_polarizations( + dtcexperiment=dtcexperiment, + circuit_list=circuit_list, + autocorrelate=autocorrelate, + take_abs=take_abs, + ), (cycles + 1, NUM_QUBITS)) + + def test_run_comparison_experiment(): """Test to check all combinations of defaults vs supplied inputs for run_comparison_experiments, with the goal of checking all paths for crashes @@ -76,7 +155,7 @@ def test_run_comparison_experiment(): for polarizations in time_crystals.run_comparison_experiment( QUBITS, cycles, disorder_instances, autocorrelate, take_abs, **kwargs ): - assert polarizations_predicate(polarizations, cycles, NUM_QUBITS) + assert polarizations_predicate(polarizations, (cycles + 1, NUM_QUBITS)) def test_signal_ratio(): From f9ad305b0db9726c02ccd0626558886c7a33de71 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 13 May 2022 04:04:30 -0700 Subject: [PATCH 18/19] Added another check for probabilities_predicate --- recirq/time_crystals/dtc_simulation_test.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/recirq/time_crystals/dtc_simulation_test.py b/recirq/time_crystals/dtc_simulation_test.py index ad5f3bd7..a481460c 100644 --- a/recirq/time_crystals/dtc_simulation_test.py +++ b/recirq/time_crystals/dtc_simulation_test.py @@ -37,6 +37,7 @@ def probabilities_predicate(probabilities, shape): probabilities.shape == shape and np.all(0 <= probabilities) and np.all(probabilities <= 1) + and np.all(np.isclose(np.sum(probabilities, axis=1), 1)) ) @@ -108,12 +109,15 @@ def test_simulate_for_polarizations(): circuit_list = time_crystals.symbolic_dtc_circuit_list(qubits=QUBITS, cycles=cycles) dtcexperiment = time_crystals.DTCExperiment(qubits=QUBITS) for autocorrelate, take_abs in itertools.product([True, False], repeat=2): - assert polarizations_predicate(time_crystals.dtc_simulation.simulate_for_polarizations( - dtcexperiment=dtcexperiment, - circuit_list=circuit_list, - autocorrelate=autocorrelate, - take_abs=take_abs, - ), (cycles + 1, NUM_QUBITS)) + assert polarizations_predicate( + time_crystals.dtc_simulation.simulate_for_polarizations( + dtcexperiment=dtcexperiment, + circuit_list=circuit_list, + autocorrelate=autocorrelate, + take_abs=take_abs, + ), + (cycles + 1, NUM_QUBITS), + ) def test_run_comparison_experiment(): From e771916a43a4131debfa74094464e20a19e0cce1 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 20 May 2022 02:41:02 -0700 Subject: [PATCH 19/19] Feedback updates --- recirq/time_crystals/dtc_simulation_test.py | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/recirq/time_crystals/dtc_simulation_test.py b/recirq/time_crystals/dtc_simulation_test.py index a481460c..f35e7053 100644 --- a/recirq/time_crystals/dtc_simulation_test.py +++ b/recirq/time_crystals/dtc_simulation_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List +from typing import List, Tuple import numpy as np import itertools @@ -21,18 +21,18 @@ import cirq import recirq.time_crystals as time_crystals -qubit_locations = [ +QUBIT_LOCATIONS = [ (3, 9), (3, 8), (3, 7), (4, 7), ] -QUBITS = [cirq.GridQubit(*idx) for idx in qubit_locations] +QUBITS = [cirq.GridQubit(*idx) for idx in QUBIT_LOCATIONS] NUM_QUBITS = len(QUBITS) -def probabilities_predicate(probabilities, shape): +def probabilities_predicate(probabilities: np.ndarray, shape: Tuple[int, int]) -> bool: return ( probabilities.shape == shape and np.all(0 <= probabilities) @@ -41,7 +41,7 @@ def probabilities_predicate(probabilities, shape): ) -def polarizations_predicate(polarizations, shape): +def polarizations_predicate(polarizations: np.ndarray, shape: Tuple[int, int]) -> bool: return ( polarizations.shape == shape and np.all(-1 <= polarizations) @@ -49,7 +49,7 @@ def polarizations_predicate(polarizations, shape): ) -def test_simulate_dtc_circuit_list_sweep(): +def test_simulate_dtc_circuit_list_sweep() -> None: cycles = 5 circuit_list = time_crystals.symbolic_dtc_circuit_list(QUBITS, cycles) param_resolvers = time_crystals.DTCExperiment(qubits=QUBITS).param_resolvers() @@ -67,7 +67,7 @@ def test_simulate_dtc_circuit_list_sweep(): ) -def test_simulate_dtc_circuit_list(): +def test_simulate_dtc_circuit_list() -> None: cycles = 5 circuit_list = time_crystals.symbolic_dtc_circuit_list(QUBITS, cycles) param_resolver = next( @@ -86,7 +86,7 @@ def test_simulate_dtc_circuit_list(): ) -def test_get_polarizations(): +def test_get_polarizations() -> None: cycles = 5 np.random.seed(5) probabilities = np.random.uniform(0, 1, (cycles, 2**NUM_QUBITS)) @@ -104,7 +104,7 @@ def test_get_polarizations(): ) -def test_simulate_for_polarizations(): +def test_simulate_for_polarizations() -> None: cycles = 5 circuit_list = time_crystals.symbolic_dtc_circuit_list(qubits=QUBITS, cycles=cycles) dtcexperiment = time_crystals.DTCExperiment(qubits=QUBITS) @@ -120,7 +120,7 @@ def test_simulate_for_polarizations(): ) -def test_run_comparison_experiment(): +def test_run_comparison_experiment() -> None: """Test to check all combinations of defaults vs supplied inputs for run_comparison_experiments, with the goal of checking all paths for crashes @@ -162,7 +162,7 @@ def test_run_comparison_experiment(): assert polarizations_predicate(polarizations, (cycles + 1, NUM_QUBITS)) -def test_signal_ratio(): +def test_signal_ratio() -> None: """Test signal_ratio function with random `np.ndarrays`""" np.random.seed(5) cycles = 100 @@ -173,7 +173,7 @@ def test_signal_ratio(): assert np.all(res >= 0) and np.all(res <= 1) -def test_symbolic_dtc_circuit_list(): +def test_symbolic_dtc_circuit_list() -> None: """Test symbolic_dtc_circuit_list function for select qubits and cycles""" cycles = 5 circuit_list = time_crystals.symbolic_dtc_circuit_list(QUBITS, cycles)