def test_numpy_types(): """ Test that we can save numpy types in the data set """ p = ParamSpecBase(name="p", paramtype="numeric") test_set = qc.new_data_set("test-dataset") test_set.set_interdependencies(InterDependencies_(standalones=(p, ))) test_set.mark_started() idps = InterDependencies_(standalones=(p, )) data_saver = DataSaver(dataset=test_set, write_period=0, interdeps=idps) dtypes = [ np.int8, np.int16, np.int32, np.int64, np.float16, np.float32, np.float64 ] for dtype in dtypes: data_saver.add_result(("p", dtype(2))) data_saver.flush_data_to_database() data = test_set.get_data("p") assert data == [[2] for _ in range(len(dtypes))]
def test_get_description(experiment, some_interdeps): ds = DataSet() assert ds.run_id == 1 desc = ds.description assert desc == RunDescriber(InterDependencies_()) ds.set_interdependencies(some_interdeps[1]) assert ds._interdeps == some_interdeps[1] # the run description gets written as the dataset is marked as started, # so now no description should be stored in the database prematurely_loaded_ds = DataSet(run_id=1) assert prematurely_loaded_ds.description == RunDescriber( InterDependencies_()) ds.mark_started() loaded_ds = DataSet(run_id=1) expected_desc = RunDescriber(some_interdeps[1]) assert loaded_ds.description == expected_desc
def test_saving_numeric_values_as_text(numeric_type): """ Test the saving numeric values into 'text' parameter raises an exception """ p = ParamSpecBase("p", "text") test_set = qc.new_data_set("test-dataset") test_set.set_interdependencies(InterDependencies_(standalones=(p, ))) test_set.mark_started() idps = InterDependencies_(standalones=(p, )) data_saver = DataSaver(dataset=test_set, write_period=0, interdeps=idps) try: value = numeric_type(2) gottype = np.array(value).dtype msg = re.escape(f'Parameter {p.name} is of type ' f'"{p.type}", but got a result of ' f'type {gottype} ({value}).') with pytest.raises(ValueError, match=msg): data_saver.add_result((p.name, value)) finally: data_saver.dataset.conn.close()
def test_new_to_old(some_paramspecbases): (ps1, ps2, ps3, ps4) = some_paramspecbases idps_new = InterDependencies_(dependencies={ps1: (ps2, ps3)}, standalones=(ps4, )) paramspec1 = ParamSpec(name=ps1.name, paramtype=ps1.type, label=ps1.label, unit=ps1.unit, depends_on=[ps2.name, ps3.name]) paramspec2 = ParamSpec(name=ps2.name, paramtype=ps2.type, label=ps2.label, unit=ps2.unit) paramspec3 = ParamSpec(name=ps3.name, paramtype=ps3.type, label=ps3.label, unit=ps3.unit) paramspec4 = ParamSpec(name=ps4.name, paramtype=ps4.type, label=ps4.label, unit=ps4.unit) idps_old_expected = InterDependencies(paramspec2, paramspec3, paramspec1, paramspec4) assert new_to_old(idps_new) == idps_old_expected # idps_new = InterDependencies_(inferences={ps1: (ps2, ps3)}, standalones=(ps4, )) paramspec1 = ParamSpec(name=ps1.name, paramtype=ps1.type, label=ps1.label, unit=ps1.unit, inferred_from=[ps2.name, ps3.name]) paramspec2 = ParamSpec(name=ps2.name, paramtype=ps2.type, label=ps2.label, unit=ps2.unit) paramspec3 = ParamSpec(name=ps3.name, paramtype=ps3.type, label=ps3.label, unit=ps3.unit) paramspec4 = ParamSpec(name=ps4.name, paramtype=ps4.type, label=ps4.label, unit=ps4.unit) idps_old_expected = InterDependencies(paramspec2, paramspec3, paramspec1, paramspec4) assert new_to_old(idps_new) == idps_old_expected
def __init__(self, exp: Optional[Experiment] = None, station: Optional[qc.Station] = None) -> None: self.exitactions: List[Tuple[Callable, Sequence]] = [] self.enteractions: List[Tuple[Callable, Sequence]] = [] self.subscribers: List[Tuple[Callable, Union[MutableSequence, MutableMapping]]] = [] self.experiment = exp self.station = station self._write_period: Optional[float] = None self.name = '' self._interdeps = InterDependencies_()
def test_init_validation_raises(some_paramspecbases): (ps1, ps2, ps3, ps4) = some_paramspecbases # First test validation of trees invalid in their own right invalid_trees = ([ps1, ps2], { 'ps1': 'ps2' }, { ps1: 'ps2' }, { ps1: ('ps2', ) }, { ps1: (ps2, ), ps2: (ps1, ) }) causes = ("ParamSpecTree must be a dict", "ParamSpecTree must have ParamSpecs as keys", "ParamSpecTree must have tuple values", "ParamSpecTree can only have tuples " "of ParamSpecs as values", "ParamSpecTree can not have cycles") for tree, cause in zip(invalid_trees, causes): with pytest.raises(ValueError, match='Invalid dependencies') as ei: InterDependencies_(dependencies=tree, inferences={}) assert error_caused_by(ei, cause=cause) for tree, cause in zip(invalid_trees, causes): with pytest.raises(ValueError, match='Invalid inferences') as ei: InterDependencies_(dependencies={}, inferences=tree) assert error_caused_by(ei, cause=cause) with pytest.raises(ValueError, match='Invalid standalones') as ei: InterDependencies_(standalones=('ps1', 'ps2')) assert error_caused_by(ei, cause='Standalones must be a sequence of ' 'ParamSpecs') # Now test trees that are invalid together invalid_trees = [{'deps': {ps1: (ps2, ps3)}, 'inffs': {ps2: (ps4, ps1)}}] for inv in invalid_trees: with pytest.raises(ValueError, match=re.escape("Invalid dependencies/inferences")): InterDependencies_(dependencies=inv['deps'], inferences=inv['inffs'])
def test_get_data_by_id_order(dataset): """ Test that the added values of setpoints end up associated with the correct setpoint parameter, irrespective of the ordering of those setpoint parameters """ indepA = ParamSpecBase('indep1', "numeric") indepB = ParamSpecBase('indep2', "numeric") depAB = ParamSpecBase('depAB', "numeric") depBA = ParamSpecBase('depBA', "numeric") idps = InterDependencies_( dependencies={depAB: (indepA, indepB), depBA: (indepB, indepA)}) dataset.set_interdependencies(idps) dataset.mark_started() dataset.add_result({'depAB': 12, 'indep2': 2, 'indep1': 1}) dataset.add_result({'depBA': 21, 'indep2': 2, 'indep1': 1}) dataset.mark_completed() data = get_data_by_id(dataset.run_id) data_dict = {el['name']: el['data'] for el in data[0]} assert data_dict['indep1'] == 1 assert data_dict['indep2'] == 2 data_dict = {el['name']: el['data'] for el in data[1]} assert data_dict['indep1'] == 1 assert data_dict['indep2'] == 2
def test_fix_wrong_run_descriptions(): v3fixpath = os.path.join(fixturepath, 'db_files', 'version3') dbname_old = os.path.join(v3fixpath, 'some_runs_without_run_description.db') if not os.path.exists(dbname_old): pytest.skip("No db-file fixtures found. You can generate test db-files" " using the scripts in the legacy_DB_generation folder") with temporarily_copied_DB(dbname_old, debug=False, version=3) as conn: assert get_user_version(conn) == 3 ds1 = DataSet(conn=conn, run_id=1) expected_description = ds1.description empty_description = RunDescriber(InterDependencies_()) _fix_wrong_run_descriptions(conn, [1, 2, 3, 4]) ds2 = DataSet(conn=conn, run_id=2) assert expected_description == ds2.description ds3 = DataSet(conn=conn, run_id=3) assert expected_description == ds3.description ds4 = DataSet(conn=conn, run_id=4) assert empty_description == ds4.description
def test_adding_too_many_results(): """ This test really tests the "chunking" functionality of the insert_many_values function of the sqlite_base module """ dataset = new_data_set("test_adding_too_many_results") xparam = ParamSpecBase("x", "numeric", label="x parameter", unit='V') yparam = ParamSpecBase("y", 'numeric', label='y parameter', unit='Hz') idps = InterDependencies_(dependencies={yparam: (xparam,)}) dataset.set_interdependencies(idps) dataset.mark_started() n_max = qc.SQLiteSettings.limits['MAX_VARIABLE_NUMBER'] vals = np.linspace(0, 1, int(n_max/2)+2) results = [{'x': val} for val in vals] dataset.add_results(results) vals = np.linspace(0, 1, int(n_max/2)+1) results = [{'x': val, 'y': val} for val in vals] dataset.add_results(results) vals = np.linspace(0, 1, n_max*3) results = [{'x': val} for val in vals] dataset.add_results(results)
def test_add_data_array(): exps = experiments() assert len(exps) == 1 exp = exps[0] assert exp.name == "test-experiment" assert exp.sample_name == "test-sample" assert exp.last_counter == 0 idps = InterDependencies_( standalones=(ParamSpecBase("x", "numeric"), ParamSpecBase("y", "array"))) mydataset = new_data_set("test") mydataset.set_interdependencies(idps) mydataset.mark_started() expected_x = [] expected_y = [] for x in range(100): expected_x.append([x]) y = np.random.random_sample(10) expected_y.append([y]) mydataset.add_result({"x": x, "y": y}) shadow_ds = make_shadow_dataset(mydataset) assert mydataset.get_data('x') == expected_x assert shadow_ds.get_data('x') == expected_x y_data = mydataset.get_data('y') np.testing.assert_allclose(y_data, expected_y) y_data = shadow_ds.get_data('y') np.testing.assert_allclose(y_data, expected_y)
def standalone_parameters_dataset(dataset): n_params = 3 n_rows = 10**3 params_indep = [ ParamSpecBase(f'param_{i}', 'numeric', label=f'param_{i}', unit='V') for i in range(n_params) ] param_dep = ParamSpecBase(f'param_{n_params}', 'numeric', label=f'param_{n_params}', unit='Ohm') params_all = params_indep + [param_dep] idps = InterDependencies_( dependencies={param_dep: tuple(params_indep[0:1])}, standalones=tuple(params_indep[1:])) dataset.set_interdependencies(idps) dataset.mark_started() dataset.add_results([{ p.name: np.int(n_rows * 10 * pn + i) for pn, p in enumerate(params_all) } for i in range(n_rows)]) dataset.mark_completed() yield dataset
def test_set_interdependencies(dataset): exps = experiments() assert len(exps) == 1 exp = exps[0] assert exp.name == "test-experiment" assert exp.sample_name == "test-sample" assert exp.last_counter == 1 parameter_a = ParamSpecBase("a_param", "NUMERIC") parameter_b = ParamSpecBase("b_param", "NUMERIC") parameter_c = ParamSpecBase("c_param", "array") idps = InterDependencies_( inferences={parameter_c: (parameter_a, parameter_b)}) dataset.set_interdependencies(idps) # write the parameters to disk dataset.mark_started() # Now retrieve the paramspecs shadow_ds = make_shadow_dataset(dataset) paramspecs = shadow_ds.paramspecs expected_keys = ['a_param', 'b_param', 'c_param'] keys = sorted(list(paramspecs.keys())) assert keys == expected_keys for expected_param_name in expected_keys: ps = paramspecs[expected_param_name] assert ps.name == expected_param_name assert paramspecs == dataset.paramspecs
def dataset_with_outliers_generator(ds, data_offset=5, low_outlier=-3, high_outlier=1, background_noise=True): x = ParamSpecBase('x', 'numeric', label='Flux', unit='e^2/hbar') t = ParamSpecBase('t', 'numeric', label='Time', unit='s') z = ParamSpecBase('z', 'numeric', label='Majorana number', unit='Anyon') idps = InterDependencies_(dependencies={z: (x, t)}) ds.set_interdependencies(idps) ds.mark_started() npoints = 50 xvals = np.linspace(0, 1, npoints) tvals = np.linspace(0, 1, npoints) for counter, xv in enumerate(xvals): if background_noise and (counter < round(npoints / 2.3) or counter > round(npoints / 1.8)): data = np.random.rand(npoints) - data_offset else: data = xv * np.linspace(0, 1, npoints) if counter == round(npoints / 1.9): data[round(npoints / 1.9)] = high_outlier if counter == round(npoints / 2.1): data[round(npoints / 2.5)] = low_outlier ds.add_results([{ 'x': xv, 't': tv, 'z': z } for z, tv in zip(data, tvals)]) ds.mark_completed() return ds
def __init__( self, enteractions: List, exitactions: List, experiment: Experiment = None, station: Station = None, write_period: numeric_types = None, interdeps: InterDependencies_ = InterDependencies_(), name: str = '', subscribers: Sequence[Tuple[Callable, Union[MutableSequence, MutableMapping]]] = None) -> None: self.enteractions = enteractions self.exitactions = exitactions self.subscribers: Sequence[Tuple[Callable, Union[MutableSequence, MutableMapping]]] if subscribers is None: self.subscribers = [] else: self.subscribers = subscribers self.experiment = experiment self.station = station self._interdependencies = interdeps # here we use 5 s as a sane default, but that value should perhaps # be read from some config file self.write_period = float(write_period) \ if write_period is not None else 5.0 self.name = name if name else 'results'
def test_numpy_nan(dataset): parameter_m = ParamSpecBase("m", "numeric") idps = InterDependencies_(standalones=(parameter_m,)) dataset.set_interdependencies(idps) dataset.mark_started() data_dict = [{"m": value} for value in [0.0, np.nan, 1.0]] dataset.add_results(data_dict) retrieved = dataset.get_data("m") assert np.isnan(retrieved[1])
def test_string_via_datasaver(experiment): """ Test that we can save text into database via DataSaver API """ p = ParamSpecBase(name="p", paramtype="text") test_set = qc.new_data_set("test-dataset") idps = InterDependencies_(standalones=(p, )) test_set.set_interdependencies(idps) test_set.mark_started() idps = InterDependencies_(standalones=(p, )) data_saver = DataSaver(dataset=test_set, write_period=0, interdeps=idps) data_saver.add_result(("p", "some text")) data_saver.flush_data_to_database() assert test_set.get_data("p") == [["some text"]]
def ds_with_vals(self, dataset): """ This fixture creates a DataSet with values that is to be used by all the tests in this class """ idps = InterDependencies_(standalones=(self.x,)) dataset.set_interdependencies(idps) dataset.mark_started() for xv in self.xvals: dataset.add_result({self.x.name: xv}) return dataset
def test_numpy_inf(dataset): """ Test that we can insert and retrieve numpy inf in the data set """ parameter_m = ParamSpecBase("m", "numeric") idps = InterDependencies_(standalones=(parameter_m,)) dataset.set_interdependencies(idps) dataset.mark_started() data_dict = [{"m": value} for value in [-np.inf, np.inf]] dataset.add_results(data_dict) retrieved = dataset.get_data("m") assert np.isinf(retrieved).all()
def test_serialize(some_paramspecbases): def tester(idps): ser = idps.serialize() json.dumps(ser) idps_deser = InterDependencies_.deserialize(ser) assert idps == idps_deser (ps1, ps2, ps3, ps4) = some_paramspecbases idps = InterDependencies_(standalones=(ps1, ps2), dependencies={ps3: (ps4, )}) tester(idps) idps = InterDependencies_(standalones=(ps1, ps2, ps3, ps4)) tester(idps) idps = InterDependencies_(dependencies={ps1: (ps2, ps3)}, inferences={ ps2: (ps4, ), ps3: (ps4, ) }) tester(idps)
def test_numpy_floats(dataset): """ Test that we can insert numpy floats in the data set """ float_param = ParamSpecBase('y', 'numeric') idps = InterDependencies_(standalones=(float_param,)) dataset.set_interdependencies(idps) dataset.mark_started() numpy_floats = [np.float, np.float16, np.float32, np.float64] results = [{"y": tp(1.2)} for tp in numpy_floats] dataset.add_results(results) expected_result = [[tp(1.2)] for tp in numpy_floats] assert np.allclose(dataset.get_data("y"), expected_result, atol=1E-8)
def test_string_with_wrong_paramtype_via_datasaver(experiment): """ Test that it is not possible to add a string value for a non-text parameter via DataSaver object """ p = ParamSpecBase("p", "numeric") test_set = qc.new_data_set("test-dataset") idps = InterDependencies_(standalones=(p, )) test_set.set_interdependencies(idps) test_set.mark_started() idps = InterDependencies_(standalones=(p, )) data_saver = DataSaver(dataset=test_set, write_period=0, interdeps=idps) try: msg = re.escape('Parameter p is of type "numeric", but got a ' "result of type <U9 (some text).") with pytest.raises(ValueError, match=msg): data_saver.add_result(("p", "some text")) finally: data_saver.dataset.conn.close()
def test_get_description(experiment, some_paramspecs): paramspecs = some_paramspecs[2] ds = DataSet() assert ds.run_id == 1 desc = ds.description assert desc == RunDescriber(InterDependencies_()) ds.add_parameter(paramspecs['ps1']) desc = ds.description expected_desc = RunDescriber( InterDependencies_(standalones=(paramspecs['ps1'].base_version(), ))) assert desc == expected_desc ds.add_parameter(paramspecs['ps2']) desc = ds.description expected_desc = RunDescriber( InterDependencies_(dependencies=({ paramspecs['ps2'].base_version(): ( paramspecs['ps1'].base_version(), ) }))) assert desc == expected_desc # the run description gets written as the dataset is marked as started, # so now no description should be stored in the database prematurely_loaded_ds = DataSet(run_id=1) assert prematurely_loaded_ds.description == RunDescriber( InterDependencies_()) ds.mark_started() loaded_ds = DataSet(run_id=1) assert loaded_ds.description == desc
def deserialize(cls, ser: Dict[str, Any]) -> 'RunDescriber': """ Make a RunDescriber object based on a serialized version of it """ # We must currently support new and old type InterDep.s objects idp: Union[InterDependencies, InterDependencies_] if 'paramspecs' in ser['interdependencies'].keys(): idp = InterDependencies.deserialize(ser['interdependencies']) else: idp = InterDependencies_.deserialize(ser['interdependencies']) rundesc = cls(interdeps=idp) return rundesc
def test_string_via_dataset(experiment): """ Test that we can save text into database via DataSet API """ p = ParamSpecBase("p", "text") test_set = qc.new_data_set("test-dataset") idps = InterDependencies_(standalones=(p, )) test_set.set_interdependencies(idps) test_set.mark_started() test_set.add_result({"p": "some text"}) test_set.mark_completed() assert test_set.get_data("p") == [["some text"]]
def __enter__(self) -> DataSaver: # TODO: should user actions really precede the dataset? # first do whatever bootstrapping the user specified for func, args in self.enteractions: func(*args) # next set up the "datasaver" if self.experiment is not None: self.ds = qc.new_data_set(self.name, self.experiment.exp_id, conn=self.experiment.conn) else: self.ds = qc.new_data_set(self.name) # .. and give the dataset a snapshot as metadata if self.station is None: station = qc.Station.default else: station = self.station if station: self.ds.add_snapshot( json.dumps({'station': station.snapshot()}, cls=NumpyJSONEncoder)) if self._interdependencies == InterDependencies_(): raise RuntimeError("No parameters supplied") else: self.ds.set_interdependencies(self._interdependencies) self.ds.mark_started() # register all subscribers for (callble, state) in self.subscribers: # We register with minimal waiting time. # That should make all subscribers be called when data is flushed # to the database log.debug(f'Subscribing callable {callble} with state {state}') self.ds.subscribe(callble, min_wait=0, min_count=1, state=state) print(f'Starting experimental run with id: {self.ds.run_id}') self.datasaver = DataSaver(dataset=self.ds, write_period=self.write_period, interdeps=self._interdependencies) return self.datasaver
def test_numpy_ints(dataset): """ Test that we can insert numpy integers in the data set """ xparam = ParamSpecBase('x', 'numeric') idps = InterDependencies_(standalones=(xparam,)) dataset.set_interdependencies(idps) dataset.mark_started() numpy_ints = [ np.int, np.int8, np.int16, np.int32, np.int64, np.uint, np.uint8, np.uint16, np.uint32, np.uint64 ] results = [{"x": tp(1)} for tp in numpy_ints] dataset.add_results(results) expected_result = len(numpy_ints) * [[1]] assert dataset.get_data("x") == expected_result
def test_add_data_1d(): exps = experiments() assert len(exps) == 1 exp = exps[0] assert exp.name == "test-experiment" assert exp.sample_name == "test-sample" assert exp.last_counter == 0 psx = ParamSpecBase("x", "numeric") psy = ParamSpecBase("y", "numeric") idps = InterDependencies_(dependencies={psy: (psx,)}) mydataset = new_data_set("test-dataset") mydataset.set_interdependencies(idps) mydataset.mark_started() expected_x = [] expected_y = [] for x in range(100): expected_x.append([x]) y = 3 * x + 10 expected_y.append([y]) mydataset.add_result({"x": x, "y": y}) shadow_ds = make_shadow_dataset(mydataset) assert mydataset.get_data('x') == expected_x assert mydataset.get_data('y') == expected_y assert shadow_ds.get_data('x') == expected_x assert shadow_ds.get_data('y') == expected_y with pytest.raises(ValueError): mydataset.add_result({'y': 500}) assert mydataset.completed is False mydataset.mark_completed() assert mydataset.completed is True with pytest.raises(CompletedError): mydataset.add_result({'y': 500}) with pytest.raises(CompletedError): mydataset.add_result({'x': 5})
def test_missing_keys(dataset): """ Test that we can now have partial results with keys missing. This is for example handy when having an interleaved 1D and 2D sweep. """ x = ParamSpecBase("x", paramtype='numeric') y = ParamSpecBase("y", paramtype='numeric') a = ParamSpecBase("a", paramtype='numeric') b = ParamSpecBase("b", paramtype='numeric') idps = InterDependencies_(dependencies={a: (x,), b: (x, y)}) dataset.set_interdependencies(idps) dataset.mark_started() def fa(xv): return xv + 1 def fb(xv, yv): return xv + 2 - yv * 3 results = [] xvals = [1, 2, 3] yvals = [2, 3, 4] for xv in xvals: results.append({"x": xv, "a": fa(xv)}) for yv in yvals: results.append({"x": xv, "y": yv, "b": fb(xv, yv)}) dataset.add_results(results) assert dataset.get_values("x") == [[r["x"]] for r in results] assert dataset.get_values("y") == [[r["y"]] for r in results if "y" in r] assert dataset.get_values("a") == [[r["a"]] for r in results if "a" in r] assert dataset.get_values("b") == [[r["b"]] for r in results if "b" in r] assert dataset.get_setpoints("a")['x'] == [[xv] for xv in xvals] tmp = [list(t) for t in zip(*(itertools.product(xvals, yvals)))] expected_setpoints = [[[v] for v in vals] for vals in tmp] assert dataset.get_setpoints("b")['x'] == expected_setpoints[0] assert dataset.get_setpoints("b")['y'] == expected_setpoints[1]
def test_basic_subscription(dataset, basic_subscriber): xparam = ParamSpecBase(name='x', paramtype='numeric', label='x parameter', unit='V') yparam = ParamSpecBase(name='y', paramtype='numeric', label='y parameter', unit='Hz') idps = InterDependencies_(dependencies={yparam: (xparam,)}) dataset.set_interdependencies(idps) dataset.mark_started() sub_id = dataset.subscribe(basic_subscriber, min_wait=0, min_count=1, state={}) assert len(dataset.subscribers) == 1 assert list(dataset.subscribers.keys()) == [sub_id] expected_state = {} for x in range(10): y = -x**2 dataset.add_result({'x': x, 'y': y}) expected_state[x+1] = [(x, y)] @retry_until_does_not_throw( exception_class_to_expect=AssertionError, delay=0, tries=10) def assert_expected_state(): assert dataset.subscribers[sub_id].state == expected_state assert_expected_state() dataset.unsubscribe(sub_id) assert len(dataset.subscribers) == 0 assert list(dataset.subscribers.keys()) == [] # Ensure the trigger for the subscriber has been removed from the database get_triggers_sql = "SELECT * FROM sqlite_master WHERE TYPE = 'trigger';" triggers = atomic_transaction( dataset.conn, get_triggers_sql).fetchall() assert len(triggers) == 0
def test_string_saved_and_loaded_as_numeric_via_dataset(experiment): """ Test that it is possible to save a string value of a non-'text' parameter via DataSet API, and, importantly, to retrieve it thanks to the flexibility of `_convert_numeric` converter function. """ p = ParamSpecBase("p", "numeric") test_set = qc.new_data_set("test-dataset") idps = InterDependencies_(standalones=(p, )) test_set.set_interdependencies(idps) test_set.mark_started() test_set.add_result({"p": 'some text'}) test_set.mark_completed() try: assert [['some text']] == test_set.get_data("p") finally: test_set.conn.close()