def test_subspace_mixed_values(): """Test that parameter spaces with parameter dimensions that have mixed values raise the expected error message. """ psp = ParamSpace( dict( all_str=ParamDim(default="foo", values=["foo", "bar", "baz"]), mixed=ParamDim(default="0", values=[0.0, "1", "2", 3, "4"]), ) ) assert psp.volume == 3 * 5 # These should fail due to a mixed-type dimension is tried to be masked with pytest.raises(TypeError, match="Could not ascertain whether"): psp.activate_subspace(mixed=0.0) with pytest.raises(TypeError, match="Could not ascertain whether"): psp.activate_subspace(all_str="foo", mixed=[0.0, "1"]) with pytest.raises(TypeError, match="Could not ascertain whether"): psp.activate_subspace(all_str="foo", mixed=["1", "2"]) # ... but this should work, as the mixed-type dimension is not touched psp.activate_subspace(all_str="foo") assert psp.volume == 1 * 5
def adv_psp(): """Used to setup a more elaborate pspace object to be tested on. Includes name clashes, manually set names, order, ...""" d = dict( a=1, b=2, foo="bar", spam="eggs", mutable=[0, 0, 0], p1=ParamDim(default=0, values=[1, 2, 3], order=0), p2=ParamDim(default=0, values=[1, 2, 3], order=1), d=dict( a=1, b=2, p1=ParamDim(default=0, values=[1, 2, 3], order=-1), p2=ParamDim(default=0, values=[1, 2, 3], order=0), d=dict( a=1, b=2, p1=ParamDim(default=0, values=[1, 2, 3], name="ppp1"), p2=ParamDim(default=0, values=[1, 2, 3], name="ppp2"), ), ), ) return ParamSpace(d)
def test_shape(small_psp, basic_psp, adv_psp, seq_psp): """Asserts that the returned shape is correct""" assert small_psp.shape == (2, 3, 5) assert basic_psp.shape == (3, 3, 3, 3, 3, 3) assert adv_psp.shape == (3, 3, 3, 3, 3, 3) assert seq_psp.shape == (3, 3, 3, 3, 3, 3, 3) p = ParamSpace( dict( a=ParamDim(default=0, values=[1]), # 1 b=ParamDim(default=0, range=[0, 10, 2]), # 5 c=ParamDim(default=0, linspace=[1, 2, 20]), # 20 d=ParamDim(default=0, logspace=[1, 2, 12, 1]), # 12 ) ) assert p.shape == (1, 5, 20, 12) # Also test the number of dimensions assert basic_psp.num_dims == 6 assert adv_psp.num_dims == 6 assert p.num_dims == 4 # And the state shape, which is +1 larger in each entry assert small_psp.states_shape == (3, 4, 6) assert basic_psp.states_shape == (4, 4, 4, 4, 4, 4) assert adv_psp.states_shape == (4, 4, 4, 4, 4, 4) # And that the maximum state number is correct assert small_psp.max_state_no == 71 assert basic_psp.max_state_no == 4095 assert adv_psp.max_state_no == 4095
def basic_psp(): """Used to setup a basic pspace object to be tested on.""" d = dict( a=1, b=2, foo="bar", spam="eggs", mutable=[0, 0, 0], p1=ParamDim(default=0, values=[1, 2, 3]), p2=ParamDim(default=0, values=[1, 2, 3]), d=dict( aa=1, bb=2, pp1=ParamDim(default=0, values=[1, 2, 3]), pp2=ParamDim(default=0, values=[1, 2, 3]), dd=dict( aaa=1, bbb=2, ppp1=ParamDim(default=0, values=[1, 2, 3]), ppp2=ParamDim(default=0, values=[1, 2, 3]), ), ), ) return ParamSpace(d)
def str_valued_psp(): """A small parameter space that has string values for parameter values""" return ParamSpace( dict( p0=ParamDim(default="foo", values=["foo", "bar", "baz"]), p1=ParamDim(default="0", values=["0", "1", "2", "3", "4"]), lin=ParamDim(default=0.0, linspace=[0.0, 1.0, 6]), ) )
def float_valued_psp(): """A small parameter space that has float values for parameter values""" return ParamSpace( dict( lin1=ParamDim(default=0.0, linspace=[-1.0, 1.0, 11]), lin2=ParamDim(default=0.0, linspace=[-2.0, 2.0, 11]), log1=ParamDim(default=0.0, logspace=[-10.0, 10.0, 6]), ) )
def small_psp(): """Used to setup a small pspace object to be tested on.""" return ParamSpace( dict( p0=ParamDim(default=0, values=[1, 2]), p1=ParamDim(default=0, values=[1, 2, 3]), p2=ParamDim(default=0, values=[1, 2, 3, 4, 5]), ) )
def test_volume(small_psp, basic_psp, adv_psp, seq_psp): """Asserts that the volume calculation is correct""" assert small_psp.volume == 2 * 3 * 5 assert basic_psp.volume == 3 ** 6 assert adv_psp.volume == 3 ** 6 assert seq_psp.volume == 3 ** 7 p = ParamSpace( dict( a=ParamDim(default=0, values=[1]), # 1 b=ParamDim(default=0, range=[0, 10, 2]), # 5 c=ParamDim(default=0, linspace=[1, 2, 20]), # 20 d=ParamDim(default=0, logspace=[1, 2, 12, 1]), # 12 ) ) assert p.volume == 1 * 5 * 20 * 12 # And of a paramspace without dimensions empty_psp = ParamSpace(dict(a=1)) assert empty_psp.volume == 0 == empty_psp.full_volume
def psp_with_coupled(): """Used to setup a pspace object with coupled param dims""" d = dict( a=ParamDim(default=0, values=[1, 2, 3], order=0), c1=CoupledParamDim(target_name=("a",)), d=dict( aa=ParamDim(default=0, values=[1, 2, 3], order=-1), cc1=CoupledParamDim(target_name=("d", "aa")), cc2=CoupledParamDim(target_name=("a",)), cc3=CoupledParamDim(target_name="aa"), ), foo="bar", spam="eggs", mutable=[0, 0, 0], ) return ParamSpace(d)
def test_coupled(psp_with_coupled): """Test parameter spaces with CoupledParamDims in them""" psp = psp_with_coupled print("ParamSpace with CoupledParamDim:\n", psp) def assert_coupling(src: tuple, target: tuple): """Asserts that the CoupledParamDim at keyseq src is coupled to the target ParamDim at keyseq target.""" assert ( psp.coupled_dims_by_loc[src].target_pdim == psp.dims_by_loc[target] ) # Assert correct coupling assert_coupling(("c1",), ("a",)) assert_coupling(("d", "cc1"), ("d", "aa")) assert_coupling(("d", "cc2"), ("a",)) assert_coupling(("d", "cc3"), ("d", "aa")) # Check default is correct default = psp.default assert default["c1"] == default["a"] assert default["d"]["cc1"] == default["d"]["aa"] assert default["d"]["cc2"] == default["a"] assert default["d"]["cc3"] == default["d"]["aa"] # Iterate over the paramspace and check correctness for pt in psp: print("Point: ", pt) assert pt["c1"] == pt["a"] assert pt["d"]["cc1"] == pt["d"]["aa"] assert pt["d"]["cc2"] == pt["a"] assert pt["d"]["cc3"] == pt["d"]["aa"] # Invalid coupling targets should raise an error with pytest.raises(ValueError, match="Could not resolve the coupling"): ParamSpace( dict( a=ParamDim(default=0, range=[10]), b=CoupledParamDim(target_name="foo"), ) )
def seq_psp(): """A parameter space with dimensions within sequences""" d = dict( a=1, b=2, foo="bar", spam="eggs", mutable=[0, 0, 0], s=[ ParamDim(default=0, values=[1, 2, 3]), [ParamDim(default=1, values=[1, 2, 3], order=1), 2, 3], ], d=dict( a=1, b=2, s=[ ParamDim(default=0, values=[1, 2, 3], order=-1), ParamDim(default=1, values=[1, 2, 3], order=0, name="ds1"), 2, 3, ], d=dict( a=1, b=2, p=ParamDim(default=0, values=[1, 2, 3]), s=[ 0, ParamDim(default=1, values=[1, 2, 3]), ParamDim(default=2, values=[1, 2, 3], name="dds2"), 3, ], ), ), ) return ParamSpace(d)
def test_init(basic_psp, adv_psp, seq_psp): """Test whether initialisation behaves as expected""" # These should work ParamSpace(dict(a=1)) ParamSpace(OrderedDict(a=1)) # These should also work, albeit not that practical ParamSpace(list(range(10))) # These should create a warning (not mutable) with pytest.warns(UserWarning, match="Got unusual type <class 'tuple'>"): ParamSpace(tuple(range(10))) with pytest.warns(UserWarning, match="Got unusual type <class 'set'>"): ParamSpace(set(range(10))) # These should warn and fail (not iterable) with pytest.raises(TypeError, match="'int' object is not iterable"): with pytest.warns(UserWarning, match="Got unusual type"): ParamSpace(1) with pytest.raises(TypeError, match="'function' object is not iterable"): with pytest.warns(UserWarning, match="Got unusual type"): ParamSpace(lambda x: None)
def test_basic_iteration(small_psp, seq_psp): """Tests whether the iteration goes through all points""" # Test on the __iter__ and __next__ level psp = small_psp it = psp.iterator() # is a generator now assert it.__next__() == dict(p0=1, p1=1, p2=1) assert psp.state_vector == (1, 1, 1) assert psp.state_no == 31 # == 24 + 6 + 1 assert it.__next__() == dict(p0=1, p1=1, p2=2) assert psp.state_vector == (1, 1, 2) assert psp.state_no == 31 + 1 assert it.__next__() == dict(p0=1, p1=1, p2=3) assert psp.state_vector == (1, 1, 3) assert psp.state_no == 31 + 2 assert it.__next__() == dict(p0=1, p1=1, p2=4) assert it.__next__() == dict(p0=1, p1=1, p2=5) assert it.__next__() == dict(p0=1, p1=2, p2=1) assert psp.state_vector == (1, 2, 1) assert psp.state_no == 31 + 6 # ... and so on psp.reset() assert psp.state_vector == (0, 0, 0) assert psp.state_no == 0 # Test some general properties relating to iteration and state # Test manually setting state vector psp.state_vector = (1, 1, 1) assert psp.current_point == dict(p0=1, p1=1, p2=1) with pytest.raises(ValueError, match="needs to be of same length as"): psp.state_vector = (0, 0) with pytest.raises(ValueError, match="Could not set the state of "): psp.state_vector = (-1, 42, 123.45) # A paramspace without volume should still be iterable empty_psp = ParamSpace(dict(foo="bar")) assert list(iter(empty_psp)) == [dict(foo="bar")] # Check the dry run psp.reset() snos = list(s for s in psp.iterator(with_info="state_no", omit_pt=True)) assert snos[:4] == [31, 32, 33, 34] # Check that the counts match using a helper function . . . . . . . . . . . def check_counts(iters, counts): cntrs = {i: 0 for i, _ in enumerate(counts)} for it_no, (it, count) in enumerate(zip(iters, counts)): for _ in it: cntrs[it_no] += 1 assert cntrs[it_no] == count # For the explicit call check_counts( (small_psp.iterator(), seq_psp.iterator()), (small_psp.volume, seq_psp.volume), ) # For the call via __iter__ and __next__ check_counts((small_psp, seq_psp), (small_psp.volume, seq_psp.volume)) # Also test all information tuples and the dry run info = ("state_no", "state_vec", "state_no_str", "current_coords") check_counts( ( small_psp.iterator(with_info=info), empty_psp.iterator(with_info=info), ), (small_psp.volume, 1), ) check_counts( (small_psp.iterator(with_info="state_no"),), (small_psp.volume,) ) # ... and whether invalid values lead to failure with pytest.raises(ValueError, match="No such information 'foo bar' avai"): info = ("state_no", "foo bar") check_counts( (small_psp.iterator(with_info=info),), (small_psp.volume,) )
def psp_nested(basic_psp): """Creates two ParamSpaces nested within another ParamSpace""" return ParamSpace( dict(foo="bar", basic=basic_psp, deeper=dict(basic=basic_psp)) )