The basics#
Before anything else, let’s import the classes we need:
try:
from respace import ResultSet
except ImportError:
!pip install respace
from respace import ResultSet
The ResultSet class#
Now let’s build the simplest possible ResultSet: it contains only one
result "result" that depends on a single parameter "parameter". And let’s make it
verbose, so we see more of what’s going on:
def add_one(parameter): return parameter + 1
rs = ResultSet({"result": add_one}, {"parameter": 1}, verbose=True)
rs
<xarray.Dataset>
Dimensions: (parameter: 1)
Coordinates:
* parameter (parameter) int64 1
Data variables:
result (parameter) int64 -1Here you can see what’s displayed is an xarray.Dataset instance, which is a
representation of your parameter space (the param_space
attribute of rs). You can see all the data you’ve entered in there, except add_one,
the computing function[1]. So where is it? Let’s select the result
and see what we got:
rs["result"]
<xarray.DataArray 'result' (parameter: 1)>
array([-1])
Coordinates:
* parameter (parameter) int64 1
Attributes:
tracking_compute_fun: <function add_one at 0x7fbbd43f5990>
computed_values: []
compute_times: []
name: result
compute_fun: <function add_one at 0x7fbc012df370>
save_fun: <function save_pickle at 0x7fbbd43d37f0>
load_fun: <function load_pickle at 0x7fbbd43d3880>
save_suffix: .pickle
save_path_fmt: NoneThere it is, under Attributes! And there’s some other stuff there too. But we’ll get
back to that. First let’s look at the “values” of the result displayed there: an array
with just a -1. But what is this doing there? Nothing was computed. Well, to find out,
let’s compute() a value of "result":
res = rs.compute("result", {})
print(res)
rs["result"]
Computing result for the following parameter values:
{'parameter': 1}
2
<xarray.DataArray 'result' (parameter: 1)>
array([0])
Coordinates:
* parameter (parameter) int64 1
Attributes:
tracking_compute_fun: <function add_one at 0x7fbbd43f5990>
computed_values: [2]
compute_times: [1.6689300537109375e-06]
name: result
compute_fun: <function add_one at 0x7fbc012df370>
save_fun: <function save_pickle at 0x7fbbd43d37f0>
load_fun: <function load_pickle at 0x7fbbd43d3880>
save_suffix: .pickle
save_path_fmt: NoneMany things have changed in here, but let’s start with the value in the array: it became
0. So what does it mean? It means that the result for parameter = 1 is located at
index 0 of the computed_values attribute: there is the 2 resulting from the
addition. But how did it know to add 1 + 1? Well since parameter was not provided in
the dictionary passed as second argument to rs.compute(), its default value was taken.
Here since the parameter has only one possible value, that’s the default. Otherwise, the
first value along the parameter axis will be the default. Let’s now add more parameter
values to see how that goes.
Changing the parameters#
rs.add_param_values({'parameter': [2, 3, 4]})
rs["result"]
<xarray.DataArray 'result' (parameter: 4)>
array([ 0, -1, -1, -1])
Coordinates:
* parameter (parameter) int64 1 2 3 4
Attributes:
tracking_compute_fun: <function add_one at 0x7fbbd43f5990>
computed_values: [2]
compute_times: [1.6689300537109375e-06]
name: result
compute_fun: <function add_one at 0x7fbc012df370>
save_fun: <function save_pickle at 0x7fbbd43d37f0>
load_fun: <function load_pickle at 0x7fbbd43d3880>
save_suffix: .pickle
save_path_fmt: NoneAs you can see, the new parameter values are there in the Coordinates, and the array’s
size increased along the exising axis, with the 0 still at the coordinate
corresponding to parameter = 1, and -1 elsewhere. Let’s see what happens if we now
try to get() the result as we did for compute above:
res = rs.get("result", {})
print(res)
rs["result"]
2
<xarray.DataArray 'result' (parameter: 4)>
array([ 0, -1, -1, -1])
Coordinates:
* parameter (parameter) int64 1 2 3 4
Attributes:
tracking_compute_fun: <function add_one at 0x7fbbd43f5990>
computed_values: [2]
compute_times: [1.6689300537109375e-06]
name: result
compute_fun: <function add_one at 0x7fbc012df370>
save_fun: <function save_pickle at 0x7fbbd43d37f0>
load_fun: <function load_pickle at 0x7fbbd43d3880>
save_suffix: .pickle
save_path_fmt: NoneHere’s the 2 again, and we didn’t get any message saying a new value was computed.
That’s because it wasn’t, since the value was already computed it was just retrieved
from the right position in computed_values. Now if we get for a different
"parameter" value:
res = rs.get("result", {"parameter": 3})
print(res)
rs["result"]
Computing result for the following parameter values:
{'parameter': 3}
4
<xarray.DataArray 'result' (parameter: 4)>
array([ 0, -1, 1, -1])
Coordinates:
* parameter (parameter) int64 1 2 3 4
Attributes:
tracking_compute_fun: <function add_one at 0x7fbbd43f5990>
computed_values: [2, 4]
compute_times: [1.6689300537109375e-06, 1.1920928955078125e-06]
name: result
compute_fun: <function add_one at 0x7fbc012df370>
save_fun: <function save_pickle at 0x7fbbd43d37f0>
load_fun: <function load_pickle at 0x7fbbd43d3880>
save_suffix: .pickle
save_path_fmt: NoneAs expected, here it computed the result for the new value.
Note
Now if we want to
see only the part of the parameter space where values have been computed, we can use the
populated_space property:
rs.populated_space["result"]
<xarray.DataArray 'result' (parameter: 2)>
array([0., 1.])
Coordinates:
* parameter (parameter) int64 1 3
Attributes:
tracking_compute_fun: <function add_one at 0x7fbbd43f5990>
computed_values: [2, 4]
compute_times: [1.6689300537109375e-06, 1.1920928955078125e-06]
name: result
compute_fun: <function add_one at 0x7fbc012df370>
save_fun: <function save_pickle at 0x7fbbd43d37f0>
load_fun: <function load_pickle at 0x7fbbd43d3880>
save_suffix: .pickle
save_path_fmt: NoneAnd what happens if we try to make a computation for a parameter value that’s not in the parameter space?
res = rs.get("result", {"parameter": 5})
print(res)
rs["result"]
Show code cell output
Computing result for the following parameter values:
{'parameter': 5}
6
<xarray.DataArray 'result' (parameter: 5)>
array([ 0, -1, 1, -1, 2])
Coordinates:
* parameter (parameter) int64 1 2 3 4 5
Attributes:
tracking_compute_fun: <function add_one at 0x7fbbd43f5990>
computed_values: [2, 4, 6]
compute_times: [1.6689300537109375e-06, 1.1920928955078125e-06, 9...
name: result
compute_fun: <function add_one at 0x7fbc012df370>
save_fun: <function save_pickle at 0x7fbbd43d37f0>
load_fun: <function load_pickle at 0x7fbbd43d3880>
save_suffix: .pickle
save_path_fmt: NoneWell it’s simply added to the set and the computation goes through.
Adding parameters#
What if we need to add some new parameters at some point? That’s what the
add_params() method is for. Here are different ways to use it
that show some of the types of parameters that can be added:
from datetime import date
from respace import Parameter
rs.add_params({"date": [date(2000, 1, 1), date.today()], "constant": 4})
rs.add_params(Parameter("letter", default="c", values=["a", "b", "c"]))
rs["result"]
<xarray.DataArray 'result' (constant: 1, date: 2, letter: 3, parameter: 5)>
array([[[[ 0, -1, 1, -1, 2],
[ 0, -1, 1, -1, 2],
[ 0, -1, 1, -1, 2]],
[[ 0, -1, 1, -1, 2],
[ 0, -1, 1, -1, 2],
[ 0, -1, 1, -1, 2]]]])
Coordinates:
* letter (letter) object 'c' 'a' 'b'
* constant (constant) int64 4
* date (date) object 2000-01-01 2023-03-19
* parameter (parameter) int64 1 2 3 4 5
Attributes:
tracking_compute_fun: <function add_one at 0x7fbbd43f5990>
computed_values: [2, 4, 6]
compute_times: [1.6689300537109375e-06, 1.1920928955078125e-06, 9...
name: result
compute_fun: <function add_one at 0x7fbc012df370>
save_fun: <function save_pickle at 0x7fbbd43d37f0>
load_fun: <function load_pickle at 0x7fbbd43d3880>
save_suffix: .pickle
save_path_fmt: NoneNote
Note how dimensions have been added to the array, and how the default value for
"letter" was shifted to the first position: that is so we always know which value is
the default.
Warning
Beware that the existing result values are then assumed to have been computed for the default value of the added parameters. So you should always make sure (and that’s usually a good programming practice!) that for the new parameters set at their default value, the behaviour of the computing function is unchanged. Also, if needed, don’t forget to update it accordingly. If parameters are absent from the signature of the function, the default behaviour implemented in ReSpace is to silently ignore these parameters[2].
Adding results#
Equivalently, you have the add_results() method to introduce
new results in the set. Here’s how you use it:
from respace import ResultMetadata
rs.add_results({"other_result": lambda parameter, constant: parameter - constant})
rs.add_results([ResultMetadata("c", lambda: 1, save_path_fmt="c")])
rs
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[12], line 3
1 from respace import ResultMetadata
----> 3 rs.add_results({"other_result": lambda parameter, constant: parameter - constant})
4 rs.add_results([ResultMetadata("c", lambda: 1, save_path_fmt="c")])
5 rs
File ~/checkouts/readthedocs.org/user_builds/respace/checkouts/stable/src/respace/result.py:831, in ResultSet.add_results(self, results_metadata)
827 data = -np.ones([len(p.values) for p in self.parameters], dtype="int")
828 add_data_vars = {
829 r.name: self._make_res_var(r, dims, data) for r in results_metadata
830 }
--> 831 self.param_space = self.param_space.assign(variables=add_data_vars)
File ~/checkouts/readthedocs.org/user_builds/respace/envs/stable/lib/python3.10/site-packages/xarray/core/dataset.py:6080, in Dataset.assign(self, variables, **variables_kwargs)
6078 data.coords._maybe_drop_multiindex_coords(set(results.keys()))
6079 # ... and then assign
-> 6080 data.update(results)
6081 return data
File ~/checkouts/readthedocs.org/user_builds/respace/envs/stable/lib/python3.10/site-packages/xarray/core/dataset.py:4946, in Dataset.update(self, other)
4910 def update(self: T_Dataset, other: CoercibleMapping) -> T_Dataset:
4911 """Update this dataset's variables with those from another dataset.
4912
4913 Just like :py:meth:`dict.update` this is a in-place operation.
(...)
4944 Dataset.merge
4945 """
-> 4946 merge_result = dataset_update_method(self, other)
4947 return self._replace(inplace=True, **merge_result._asdict())
File ~/checkouts/readthedocs.org/user_builds/respace/envs/stable/lib/python3.10/site-packages/xarray/core/merge.py:1104, in dataset_update_method(dataset, other)
1101 if coord_names:
1102 other[key] = value.drop_vars(coord_names)
-> 1104 return merge_core(
1105 [dataset, other],
1106 priority_arg=1,
1107 indexes=dataset.xindexes,
1108 combine_attrs="override",
1109 )
File ~/checkouts/readthedocs.org/user_builds/respace/envs/stable/lib/python3.10/site-packages/xarray/core/merge.py:761, in merge_core(objects, compat, join, combine_attrs, priority_arg, explicit_coords, indexes, fill_value)
756 prioritized = _get_priority_vars_and_indexes(aligned, priority_arg, compat=compat)
757 variables, out_indexes = merge_collected(
758 collected, prioritized, compat=compat, combine_attrs=combine_attrs
759 )
--> 761 dims = calculate_dimensions(variables)
763 coord_names, noncoord_names = determine_coords(coerced)
764 if explicit_coords is not None:
File ~/checkouts/readthedocs.org/user_builds/respace/envs/stable/lib/python3.10/site-packages/xarray/core/variable.py:3216, in calculate_dimensions(variables)
3214 last_used[dim] = k
3215 elif dims[dim] != size:
-> 3216 raise ValueError(
3217 f"conflicting sizes for dimension {dim!r}: "
3218 f"length {size} on {k!r} and length {dims[dim]} on {last_used!r}"
3219 )
3220 return dims
ValueError: conflicting sizes for dimension 'letter': length 1 on 'other_result' and length 3 on {'letter': 'letter', 'constant': 'constant', 'date': 'date', 'parameter': 'parameter'}
Saving results#
ReSpace also makes it super easy for you to save your results, let’s have a look:
_ = rs.save("result", {"parameter": 5})
_ = rs.save("c", {})
Saving result at result_letter=c_constant=4_date=2000-01-01_parameter=5.pickle.
Computing c for the following parameter values:
{'letter': 'c', 'constant': 4, 'date': datetime.date(2000, 1, 1), 'parameter': 1}
Saving c at c.pickle.
The result "c" was saved according to the save path format we passed it. More
interestingly, "result" was saved at a path indicating first its name, and then a
string giving the name of the parameters and their values.