Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions recirq/time_crystals/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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.dtcexperiment import (DTCExperiment, comparison_experiments,
EXPERIMENT_NAME, DEFAULT_BASE_DIR)

144 changes: 144 additions & 0 deletions recirq/time_crystals/dtcexperiment.py
Original file line number Diff line number Diff line change
@@ -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)
66 changes: 66 additions & 0 deletions recirq/time_crystals/dtcexperiment_test.py
Original file line number Diff line number Diff line change
@@ -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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't be fancy like this with tests. Keep them "DAMP" (descriptive and meaningful phrases) rather than "DRY" (don't repeat yourself)

In addition to being clunky, this test is broken when I run it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests should be working properly now, and the code is less fancy.
What they do, however, hasn't changed. They still simply run every combination of default or supplied values for path coverage. This could be improved but I'm not sure how would be best.

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()