From f93cd08cc4cfd4faefe6af2b5b49a0a907669816 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Tue, 21 Dec 2021 21:53:45 -0800 Subject: [PATCH 01/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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 e38c475f791894cca16b80af52a1404ce10cf8ac Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Wed, 22 Dec 2021 14:40:27 -0800 Subject: [PATCH 13/17] Add circuit generation notebook --- .../time_crystal_circuit_generation.ipynb | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs/time_crystals/time_crystal_circuit_generation.ipynb diff --git a/docs/time_crystals/time_crystal_circuit_generation.ipynb b/docs/time_crystals/time_crystal_circuit_generation.ipynb new file mode 100644 index 00000000..1bd011fb --- /dev/null +++ b/docs/time_crystals/time_crystal_circuit_generation.ipynb @@ -0,0 +1,160 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "10a61b4d73a5" + }, + "outputs": [], + "source": [ + "# Copyright 2022 Google\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7763e5d20492" + }, + "source": [ + "# Time Crystal Circuit Generation\n", + "This notebook covers how Many Body Local Discrete Time Crystal circuit lists are created, from the paper: Observation of Time-Crystalline Eigenstate Order on a Quantum Processor ([arxiv:2107.13571](https://arxiv.org/abs/2107.13571)). \n", + "\n", + "Quantum computers, and gate-based quantum circuits turn out to be well suited for crafting systems that exhibit time-crystalline behavior. Behavior is crystalline with respect to time if it has some consistent and stable pattern over time. This system's pattern must be resilient against perturbation, including the introduction of random noise and heat, in the same way that a space-crystalline object, like a diamond, stays in shape if moved or heated. \n", + "\n", + "The implementation of a time-crystalline system on a quantum computer starts with a set of qubits that are connected in a single chain. This serves as a many body local system which, at the low superconducting temperature that the qubits are run at, becomes resistant to changes cause by small amounts of introduced heat. The heat's energy is spread across the qubits, thin enough that it is not sufficient to change the qubits' states. The amount of heat dissipation in the DTC circuits is modeled with the thermalization constant `g`. \n", + "\n", + "The actual time-crystalline behavior that the DTC circuits implement is perhaps the simplest possible kind of time-structured behavior, oscillation. Each circuit is built with some number of identical $U$-cycles. Time is represented by a circuit list where each circuit is ordered with increasingly many $U$-cycles; each cycle is a discrete time step. The eventual effect of these $U$-cycles, as demonstrated in the paper and in the [Time Crystal Data Analysis](time_crystal_data_analysis.ipynb) notebook, is consistent oscillations of each qubits' polarizations. Each $U$-cycle includes a component to model to model the influence of thermalization, with the constant `g`, and random noise, with the `local_fields` variables. The behavior of the DTC circuits is shown to be time-crystalline in spite of the consistently introduced randomness. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f53722cb0850" + }, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0e4595827ec0" + }, + "outputs": [], + "source": [ + "!pip install cirq --pre --quiet\n", + "try:\n", + " import recirq\n", + "except ImportError:\n", + " !pip install --quiet git+https://github.com/quantumlib/ReCirq" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "e5e4f66e8e67" + }, + "outputs": [], + "source": [ + "import cirq\n", + "import recirq.time_crystals as time_crystals" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "a12b700f009e" + }, + "source": [ + "## Circuit Construction\n", + "Each DTC circuit is created with symbolic parameters. Parameter values are supplied near run/simulation time with a `cirq.ParamResolver`, which means the circuit list needs to be generated only once for potentially many different experimental parameter configurations. \n", + "\n", + "The code below uses an IPython-specific utility to inspect the code of the key function that creates the symbolic circuit list, `recirq.time_crystals.symbolic_dtc_circuit_list()`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "c0f1c1475908" + }, + "outputs": [], + "source": [ + "import inspect\n", + "from IPython.display import Code\n", + "\n", + "Code(inspect.getsource(time_crystals.symbolic_dtc_circuit_list), language=\"python\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e13de6da1fc6" + }, + "source": [ + "The construction of each circuit is surprisingly short. \n", + "\n", + "The circuit expects the quantum computer to be in the all-zeros state, and starts with a sequence of `cirq.Y` gates conditioned on the provided `initial state` parameter, after initializing the necessary symbolic variables. \n", + "\n", + "Each $U$-cycle consists of three moments. First, a moment of `cirq.PhasedXZGate`s, with one for each qubit. Each `cirq.PhasedXZGate` takes the thermalization constant `g` as its X-exponent, and the random variance provided by `local_fields` for its Y-exponent. This moment collectively models the disorder caused by heat and random noise. \n", + "\n", + "The second and third moments both cause the oscillation behavior and compensate for the first disorder moment. The qubits are connected in a chain, and each qubit pair connection in that chain is coupled with a `cirq.PhasedFSimGate` that uses the parameters `[theta, zetas, chi, gamma, phi]`. To keep gates from overlapping on the same qubit, this chain of gates is split into the second and third moments, such that no two gates share a qubit within each moment. \n", + "\n", + "Finally, `symbolic_dtc_circuit_list()` builds and returns a list of circuits with $0,1,2,..., cycles$ many $U$-cycles in them. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8397a5eab5f7" + }, + "outputs": [], + "source": [ + "qubits = [cirq.GridQubit(0, i) for i in range(4)]\n", + "circuit_list = time_crystals.symbolic_dtc_circuit_list(qubits, 2)\n", + "for circuit in circuit_list:\n", + " print(\"\\ncircuit of length \" + str(len(circuit)) + \"\\n\")\n", + " print(circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3ba0e882df83" + }, + "source": [ + "After the initial line of `cirq.Y` gates, each consecutive circuit in the list has an additional cycle of `cirq.PhasedXZGate`s, followed by the chain of `cirq.PhasedFSimGate`s on alternating qubit pairs. Each cycle of three moments becomes one time step in the later analysis of stable oscillations over time. \n", + "\n", + "The next step is to perform experiments to collect evidence of the time-crystalline behavior of the quantum state's polarizations. See the [Time Crystal Data Collection](time_crystal_data_collection.ipynb) notebook for the experiments, and the [Time Crystal Data Analysis](time_crystal_data_analysis.ipynb) notebook for the graphed data and results. " + ] + } + ], + "metadata": { + "colab": { + "name": "time_crystal_circuit_generation.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From 066d5dcbedc7e58c160e0841ff14b62c28deab06 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Wed, 22 Dec 2021 18:37:56 -0800 Subject: [PATCH 14/17] Corrections: terminology and technical correctness --- .../time_crystal_circuit_generation.ipynb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/time_crystals/time_crystal_circuit_generation.ipynb b/docs/time_crystals/time_crystal_circuit_generation.ipynb index 1bd011fb..ddcb156d 100644 --- a/docs/time_crystals/time_crystal_circuit_generation.ipynb +++ b/docs/time_crystals/time_crystal_circuit_generation.ipynb @@ -30,13 +30,13 @@ }, "source": [ "# Time Crystal Circuit Generation\n", - "This notebook covers how Many Body Local Discrete Time Crystal circuit lists are created, from the paper: Observation of Time-Crystalline Eigenstate Order on a Quantum Processor ([arxiv:2107.13571](https://arxiv.org/abs/2107.13571)). \n", + "This notebook covers how Many Body Local Discrete Time Crystal circuit lists are created, from the paper: Observation of Time-Crystalline Eigenstate Order on a Quantum Processor ([Nature](https://www.nature.com/articles/s41586-021-04257-w)). \n", "\n", - "Quantum computers, and gate-based quantum circuits turn out to be well suited for crafting systems that exhibit time-crystalline behavior. Behavior is crystalline with respect to time if it has some consistent and stable pattern over time. This system's pattern must be resilient against perturbation, including the introduction of random noise and heat, in the same way that a space-crystalline object, like a diamond, stays in shape if moved or heated. \n", + "Quantum computers and gate-based quantum circuits turn out to be well suited for crafting systems that exhibit time-crystalline behavior. Behavior is crystalline with respect to time if it has some consistent and stable pattern over time. This system's pattern must be resilient against perturbation in the same way that a space-crystalline object, like a diamond, is still a diamond if moved or heated. \n", "\n", - "The implementation of a time-crystalline system on a quantum computer starts with a set of qubits that are connected in a single chain. This serves as a many body local system which, at the low superconducting temperature that the qubits are run at, becomes resistant to changes cause by small amounts of introduced heat. The heat's energy is spread across the qubits, thin enough that it is not sufficient to change the qubits' states. The amount of heat dissipation in the DTC circuits is modeled with the thermalization constant `g`. \n", + "The quantum computer supplies a system of many qubits, locally connected to each other in a chain. A many-body local system like this is critical for the existence of a time crystal. Without an MBL system, it is expected that the system's state would decay into a maximum entropy state that is incompatible with the goal of stable and consistent time structure. \n", "\n", - "The actual time-crystalline behavior that the DTC circuits implement is perhaps the simplest possible kind of time-structured behavior, oscillation. Each circuit is built with some number of identical $U$-cycles. Time is represented by a circuit list where each circuit is ordered with increasingly many $U$-cycles; each cycle is a discrete time step. The eventual effect of these $U$-cycles, as demonstrated in the paper and in the [Time Crystal Data Analysis](time_crystal_data_analysis.ipynb) notebook, is consistent oscillations of each qubits' polarizations. Each $U$-cycle includes a component to model to model the influence of thermalization, with the constant `g`, and random noise, with the `local_fields` variables. The behavior of the DTC circuits is shown to be time-crystalline in spite of the consistently introduced randomness. " + "The time-crystalline behavior that the DTC circuits demonstrate is perhaps the simplest kind of time-structured behavior, oscillation. Each circuit is built with some number of identical $U$-cycles. Time is represented by a circuit list where each circuit is ordered with increasingly many $U$-cycles; each cycle is a discrete time step. The eventual effect of these $U$-cycles is consistent oscillations of each qubits' polarizations. The experiments performed demonstrate that this time-crystalline oscillation behavior is stable in spite of different initial states and introduced random potentials. " ] }, { @@ -107,11 +107,11 @@ "id": "e13de6da1fc6" }, "source": [ - "The construction of each circuit is surprisingly short. \n", + "The construction of each circuit is surprisingly succinct. \n", "\n", "The circuit expects the quantum computer to be in the all-zeros state, and starts with a sequence of `cirq.Y` gates conditioned on the provided `initial state` parameter, after initializing the necessary symbolic variables. \n", "\n", - "Each $U$-cycle consists of three moments. First, a moment of `cirq.PhasedXZGate`s, with one for each qubit. Each `cirq.PhasedXZGate` takes the thermalization constant `g` as its X-exponent, and the random variance provided by `local_fields` for its Y-exponent. This moment collectively models the disorder caused by heat and random noise. \n", + "Each $U$-cycle consists of three moments. First, a moment of `cirq.PhasedXZGate`s, with one for each qubit. Each `cirq.PhasedXZGate` takes the control parameter `g` as its X-exponent, and the random potentials necessary for many-body localization provided by `local_fields` for its Y-exponent.\n", "\n", "The second and third moments both cause the oscillation behavior and compensate for the first disorder moment. The qubits are connected in a chain, and each qubit pair connection in that chain is coupled with a `cirq.PhasedFSimGate` that uses the parameters `[theta, zetas, chi, gamma, phi]`. To keep gates from overlapping on the same qubit, this chain of gates is split into the second and third moments, such that no two gates share a qubit within each moment. \n", "\n", From c59a240cb708dd1e5666677733e9f7aceeadbe05 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Sun, 21 Aug 2022 01:51:52 -0700 Subject: [PATCH 15/17] Improve explanations a bit --- .../time_crystal_circuit_generation.ipynb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/time_crystals/time_crystal_circuit_generation.ipynb b/docs/time_crystals/time_crystal_circuit_generation.ipynb index ddcb156d..8c5953e4 100644 --- a/docs/time_crystals/time_crystal_circuit_generation.ipynb +++ b/docs/time_crystals/time_crystal_circuit_generation.ipynb @@ -32,11 +32,11 @@ "# Time Crystal Circuit Generation\n", "This notebook covers how Many Body Local Discrete Time Crystal circuit lists are created, from the paper: Observation of Time-Crystalline Eigenstate Order on a Quantum Processor ([Nature](https://www.nature.com/articles/s41586-021-04257-w)). \n", "\n", - "Quantum computers and gate-based quantum circuits turn out to be well suited for crafting systems that exhibit time-crystalline behavior. Behavior is crystalline with respect to time if it has some consistent and stable pattern over time. This system's pattern must be resilient against perturbation in the same way that a space-crystalline object, like a diamond, is still a diamond if moved or heated. \n", + "Quantum computers and gate-based quantum circuits turn out to be well suited for crafting systems that exhibit time-crystalline behavior. Behavior is crystalline with respect to time if it has some consistent and stable pattern over time. This system's pattern must be resilient against perturbation in the same way that a space-crystalline object, like a diamond, maintains it's structure (is still a diamond) if moved or heated. \n", "\n", "The quantum computer supplies a system of many qubits, locally connected to each other in a chain. A many-body local system like this is critical for the existence of a time crystal. Without an MBL system, it is expected that the system's state would decay into a maximum entropy state that is incompatible with the goal of stable and consistent time structure. \n", "\n", - "The time-crystalline behavior that the DTC circuits demonstrate is perhaps the simplest kind of time-structured behavior, oscillation. Each circuit is built with some number of identical $U$-cycles. Time is represented by a circuit list where each circuit is ordered with increasingly many $U$-cycles; each cycle is a discrete time step. The eventual effect of these $U$-cycles is consistent oscillations of each qubits' polarizations. The experiments performed demonstrate that this time-crystalline oscillation behavior is stable in spite of different initial states and introduced random potentials. " + "The time-crystalline behavior that the DTC circuits demonstrate is perhaps the simplest kind of time-structured behavior, oscillation. Each circuit is built with some number of identical $U$-cycles. Time is represented by a circuit list where each circuit is ordered with increasingly many $U$-cycles; each cycle is considered a discrete time step. The eventual effect of these $U$-cycles is consistent oscillations of each qubits' polarizations. The experiments performed demonstrate that this time-crystalline oscillation behavior is stable in spite of different initial states and introduced random potentials. " ] }, { @@ -56,7 +56,7 @@ }, "outputs": [], "source": [ - "!pip install cirq --pre --quiet\n", + "!pip install cirq --quiet\n", "try:\n", " import recirq\n", "except ImportError:\n", @@ -113,7 +113,7 @@ "\n", "Each $U$-cycle consists of three moments. First, a moment of `cirq.PhasedXZGate`s, with one for each qubit. Each `cirq.PhasedXZGate` takes the control parameter `g` as its X-exponent, and the random potentials necessary for many-body localization provided by `local_fields` for its Y-exponent.\n", "\n", - "The second and third moments both cause the oscillation behavior and compensate for the first disorder moment. The qubits are connected in a chain, and each qubit pair connection in that chain is coupled with a `cirq.PhasedFSimGate` that uses the parameters `[theta, zetas, chi, gamma, phi]`. To keep gates from overlapping on the same qubit, this chain of gates is split into the second and third moments, such that no two gates share a qubit within each moment. \n", + "The second and third moments together both cause the oscillation behavior and compensate for the first disorder moment. The qubits are connected in a chain, and each qubit pair connection in that chain is coupled with a `cirq.PhasedFSimGate` that uses the parameters `[theta, zetas, chi, gamma, phi]`. To keep gates from overlapping on the same qubit, this chain of gates is split into the second and third moments, such that no two gates share a qubit within each moment. \n", "\n", "Finally, `symbolic_dtc_circuit_list()` builds and returns a list of circuits with $0,1,2,..., cycles$ many $U$-cycles in them. " ] @@ -141,7 +141,7 @@ "source": [ "After the initial line of `cirq.Y` gates, each consecutive circuit in the list has an additional cycle of `cirq.PhasedXZGate`s, followed by the chain of `cirq.PhasedFSimGate`s on alternating qubit pairs. Each cycle of three moments becomes one time step in the later analysis of stable oscillations over time. \n", "\n", - "The next step is to perform experiments to collect evidence of the time-crystalline behavior of the quantum state's polarizations. See the [Time Crystal Data Collection](time_crystal_data_collection.ipynb) notebook for the experiments, and the [Time Crystal Data Analysis](time_crystal_data_analysis.ipynb) notebook for the graphed data and results. " + "The next step is to perform experiments to collect evidence of the time-crystalline behavior of the quantum state's polarizations. See the [Time Crystal Data Collection](time_crystal_data_collection) notebook for the experiments, and the [Time Crystal Data Analysis](time_crystal_data_analysis) notebook for the graphed data and results. " ] } ], From 6eca0a569cd8aa7963b9238d71249c55f8f43837 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Sun, 21 Aug 2022 16:24:40 -0700 Subject: [PATCH 16/17] Remove old dtcsimulation file (now dtc_simulation) --- recirq/time_crystals/dtcsimulation.py | 353 --------------------- recirq/time_crystals/dtcsimulation_test.py | 104 ------ 2 files changed, 457 deletions(-) delete mode 100644 recirq/time_crystals/dtcsimulation.py delete mode 100644 recirq/time_crystals/dtcsimulation_test.py diff --git a/recirq/time_crystals/dtcsimulation.py b/recirq/time_crystals/dtcsimulation.py deleted file mode 100644 index db00a311..00000000 --- a/recirq/time_crystals/dtcsimulation.py +++ /dev/null @@ -1,353 +0,0 @@ -# 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. - -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 - - -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], -) -> Iterator[np.ndarray]: - """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) -> 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, -) -> np.ndarray: - """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, -) -> Iterator[np.ndarray]: - """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 deleted file mode 100644 index 86dc4dfc..00000000 --- a/recirq/time_crystals/dtcsimulation_test.py +++ /dev/null @@ -1,104 +0,0 @@ -# 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 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 fdf7c0e5a5b3e5a053bd54ce8e55cbcab196d946 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Mon, 22 Aug 2022 15:10:27 -0700 Subject: [PATCH 17/17] Feedback fixes --- docs/time_crystals/time_crystal_circuit_generation.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/time_crystals/time_crystal_circuit_generation.ipynb b/docs/time_crystals/time_crystal_circuit_generation.ipynb index 8c5953e4..5e950a16 100644 --- a/docs/time_crystals/time_crystal_circuit_generation.ipynb +++ b/docs/time_crystals/time_crystal_circuit_generation.ipynb @@ -32,11 +32,11 @@ "# Time Crystal Circuit Generation\n", "This notebook covers how Many Body Local Discrete Time Crystal circuit lists are created, from the paper: Observation of Time-Crystalline Eigenstate Order on a Quantum Processor ([Nature](https://www.nature.com/articles/s41586-021-04257-w)). \n", "\n", - "Quantum computers and gate-based quantum circuits turn out to be well suited for crafting systems that exhibit time-crystalline behavior. Behavior is crystalline with respect to time if it has some consistent and stable pattern over time. This system's pattern must be resilient against perturbation in the same way that a space-crystalline object, like a diamond, maintains it's structure (is still a diamond) if moved or heated. \n", + "Quantum computers and gate-based quantum circuits turn out to be well suited for crafting systems that exhibit time-crystalline behavior. Behavior is crystalline with respect to time if it has some consistent and stable pattern over time. This system's pattern must be resilient against perturbation in the same way that a space-crystalline object, like a diamond, maintains its structure (is still a diamond) if moved or heated. \n", "\n", - "The quantum computer supplies a system of many qubits, locally connected to each other in a chain. A many-body local system like this is critical for the existence of a time crystal. Without an MBL system, it is expected that the system's state would decay into a maximum entropy state that is incompatible with the goal of stable and consistent time structure. \n", + "The quantum computer supplies a system of many qubits, locally connected to each other in a chain. A many-body local (MBL) system like this is critical for the existence of a time crystal. Without an MBL system, it is expected that the system's state would decay into a maximum entropy state that is incompatible with the goal of stable and consistent time structure. \n", "\n", - "The time-crystalline behavior that the DTC circuits demonstrate is perhaps the simplest kind of time-structured behavior, oscillation. Each circuit is built with some number of identical $U$-cycles. Time is represented by a circuit list where each circuit is ordered with increasingly many $U$-cycles; each cycle is considered a discrete time step. The eventual effect of these $U$-cycles is consistent oscillations of each qubits' polarizations. The experiments performed demonstrate that this time-crystalline oscillation behavior is stable in spite of different initial states and introduced random potentials. " + "The time-crystalline behavior that the discrete time crystal (DTC) circuits demonstrate is perhaps the simplest kind of time-structured behavior, oscillation. Each circuit is built with some number of identical $U$-cycles. Time is represented by a circuit list where each circuit is ordered with increasingly many $U$-cycles; each cycle is considered a discrete time step. The eventual effect of these $U$-cycles is consistent oscillations of each qubits' polarizations. The experiments performed demonstrate that this time-crystalline oscillation behavior is stable in spite of different initial states and introduced random potentials. " ] }, {