Skip to content

Commit f1ce409

Browse files
Fix silent OOB params post-optimization failure (#236)
* Make Qty raise more specific OOB exception * Renamed exception to error * Added extend_bounds arg to load_best method * Made pmap better cach and raise OOB exception * Made optimizer raise error if opt params are OOB * Linting * Created test to ensure that OOB optimizations are caught * Updated changelog * Linting * Made test be more general and robust * Allow float-esque equality * Changed/set extend_bounds defaults * Use pytest.raises
1 parent 2d95723 commit f1ce409

File tree

8 files changed

+93
-14
lines changed

8 files changed

+93
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This Changelog tracks all past changes to this project as well as details about
2424
- `fixed` bug where changing a gate's name didn't change its ideal gate #233
2525
- `fixed` bug in Instruction where ideal gate's weren't properly set #229
2626
- `fixed` wrong direction of rotations on bloch sphere #231
27+
- `fixed` error is raised if optimizer gives OOB results #235
2728

2829
## Version `1.4` - 23 Dec 2021
2930

c3/c3objs.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
import copy
1010

1111

12+
class QuantityOOBException(ValueError):
13+
pass
14+
15+
1216
class C3obj:
1317
"""
1418
Represents an abstract object with parameters. To be inherited from.
@@ -281,8 +285,16 @@ def _set_value(self, val) -> None:
281285
2 * (tf.reshape(val, self.shape) * self.pref - self.offset) / self.scale - 1
282286
)
283287

284-
if np.any(tf.math.abs(tmp) > tf.constant(1.0, tf.float64)):
285-
raise ValueError(
288+
const_1 = tf.constant(1.0, tf.float64)
289+
if np.any(
290+
tf.math.logical_and(
291+
tf.math.abs(tmp) > const_1,
292+
tf.math.logical_not(
293+
tf.experimental.numpy.isclose(tf.math.abs(tmp), const_1)
294+
),
295+
)
296+
):
297+
raise QuantityOOBException(
286298
f"Value {num3str(val.numpy())}{self.unit} out of bounds for quantity with "
287299
f"min_val: {num3str(self.get_limits()[0])}{self.unit} and "
288300
f"max_val: {num3str(self.get_limits()[1])}{self.unit}",

c3/optimizers/optimalcontrol.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22

33
import os
44
import shutil
5+
56
import tensorflow as tf
67
from typing import Callable, List
78

89
from c3.optimizers.optimizer import Optimizer
10+
from c3.parametermap import ParameterMapOOBUpdateException
911
from c3.utils.utils import log_setup
1012

1113
from c3.libraries.algorithms import algorithms
1214
from c3.libraries.fidelities import fidelities
1315

1416

17+
class OptResultOOBError(Exception):
18+
pass
19+
20+
1521
class OptimalControl(Optimizer):
1622
"""
1723
Object that deals with the open loop optimal control.
@@ -179,7 +185,16 @@ def optimize_controls(self, setup_log: bool = True) -> None:
179185
)
180186
except KeyboardInterrupt:
181187
pass
182-
self.load_best(self.logdir + "best_point_" + self.logname)
188+
189+
try:
190+
self.load_best(
191+
self.logdir + "best_point_" + self.logname, extend_bounds=False
192+
)
193+
except ParameterMapOOBUpdateException as e:
194+
raise OptResultOOBError(
195+
"The optimization resulted in some of the parameters being out of bounds."
196+
) from e
197+
183198
self.end_log()
184199

185200
@tf.function

c3/optimizers/optimizer.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def set_created_by(self, config) -> None:
102102
"""
103103
self.created_by = config
104104

105-
def load_best(self, init_point) -> None:
105+
def load_best(self, init_point, extend_bounds=False) -> None:
106106
"""
107107
Load a previous parameter point to start the optimization from. Legacy wrapper.
108108
Method moved to Parametermap.
@@ -111,9 +111,10 @@ def load_best(self, init_point) -> None:
111111
----------
112112
init_point : str
113113
File location of the initial point
114-
114+
extend_bounds: bool
115+
Whether or not to allow the loaded optimal parameters' bounds to be extended if they exceed those specified.
115116
"""
116-
self.pmap.load_values(init_point)
117+
self.pmap.load_values(init_point, extend_bounds=extend_bounds)
117118

118119
def start_log(self) -> None:
119120
"""

c3/parametermap.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
import copy
66
import numpy as np
77
import tensorflow as tf
8-
from c3.c3objs import Quantity, hjson_decode, hjson_encode
8+
from c3.c3objs import Quantity, hjson_decode, hjson_encode, QuantityOOBException
99
from c3.signal.gates import Instruction
1010
from typing import Union
1111
from tensorflow.errors import InvalidArgumentError
1212

1313

14+
class ParameterMapOOBUpdateException(Exception):
15+
pass
16+
17+
1418
class ParameterMap:
1519
"""
1620
Collects information about control and model parameters and provides different
@@ -66,22 +70,24 @@ def __initialize_parameters(self) -> None:
6670
def update_parameters(self):
6771
self.__initialize_parameters()
6872

69-
def load_values(self, init_point):
73+
def load_values(self, init_point, extend_bounds=False):
7074
"""
7175
Load a previous parameter point to start the optimization from.
7276
7377
Parameters
7478
----------
7579
init_point : str
7680
File location of the initial point
77-
81+
extend_bounds: bool
82+
Whether or not to allow the loaded parameters' bounds to be extended if they exceed those specified.
7883
"""
7984
with open(init_point) as init_file:
8085
best = hjson.load(init_file, object_pairs_hook=hjson_decode)
8186

8287
best_opt_map = best["opt_map"]
8388
init_p = best["optim_status"]["params"]
84-
self.set_parameters(init_p, best_opt_map, extend_bounds=True)
89+
90+
self.set_parameters(init_p, best_opt_map, extend_bounds=extend_bounds)
8591

8692
def store_values(self, path: str, optim_status=None) -> None:
8793
"""
@@ -308,9 +314,9 @@ def set_parameters(
308314
raise Exception(f"C3:ERROR:{key} not defined.") from ve
309315
try:
310316
par.set_value(values[val_indx], extend_bounds=extend_bounds)
311-
except (ValueError, InvalidArgumentError) as ve:
317+
except (QuantityOOBException, InvalidArgumentError) as ve:
312318
try:
313-
raise Exception(
319+
raise ParameterMapOOBUpdateException(
314320
f"C3:ERROR:Trying to set {key} "
315321
f"to value {values[val_indx]} "
316322
f"but has to be within {par.offset} .."

test/test_model_learning.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ def test_model_learning() -> None:
5454
if "xy_angle" in pulse.params:
5555
pulse.params["xy_angle"] *= -1
5656

57-
5857
opt = ModelLearning(**cfg, pmap=exp.pmap)
5958
opt.set_exp(exp)
6059
opt.set_created_by(OPT_CONFIG_FILE_NAME)

test/test_optimalcontrol.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import numpy as np
2+
import pytest
3+
4+
import c3.libraries.fidelities as fidelities
5+
from c3.optimizers.optimalcontrol import OptimalControl, OptResultOOBError
6+
from examples.single_qubit_experiment import create_experiment
7+
8+
9+
@pytest.mark.integration
10+
def test_raises_OOB_for_bad_optimizer() -> None:
11+
exp = create_experiment()
12+
exp.set_opt_gates(["rx90p[0]"])
13+
14+
opt_gates = ["rx90p[0]"]
15+
gateset_opt_map = [
16+
[
17+
("rx90p[0]", "d1", "carrier", "framechange"),
18+
],
19+
[
20+
("rx90p[0]", "d1", "gauss", "amp"),
21+
],
22+
[
23+
("rx90p[0]", "d1", "gauss", "freq_offset"),
24+
],
25+
]
26+
27+
exp.pmap.set_opt_map(gateset_opt_map)
28+
29+
def bad_alg(x_init, fun, *args, **kwargs):
30+
res = np.array([x * -99 for x in x_init])
31+
fun(res)
32+
return res
33+
34+
opt = OptimalControl(
35+
fid_func=fidelities.unitary_infid_set,
36+
fid_subspace=["Q1"],
37+
pmap=exp.pmap,
38+
algorithm=bad_alg,
39+
)
40+
41+
exp.set_opt_gates(opt_gates)
42+
opt.set_exp(exp)
43+
44+
with pytest.raises(OptResultOOBError):
45+
opt.optimize_controls()

test/test_parameter_map.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def test_parameter_extend() -> None:
256256
Test the setting in optimizer format. Parameter is out of bounds for the
257257
original pmap and should be extended.
258258
"""
259-
pmap.load_values("test/sample_optim_log.c3log")
259+
pmap.load_values("test/sample_optim_log.c3log",extend_bounds=True)
260260
np.testing.assert_almost_equal(
261261
pmap.get_parameter(("rx90p[0]", "d1", "gauss", "freq_offset")).numpy(),
262262
-82997604.24565414,

0 commit comments

Comments
 (0)