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 0x7fa8ef2b8040>
computed_values: []
compute_times: []
name: result
compute_fun: <function add_one at 0x7fa91c0896c0>
save_fun: <function save_pickle at 0x7fa8ef285e10>
load_fun: <function load_pickle at 0x7fa8ef285ea0>
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 0x7fa8ef2b8040>
computed_values: [2]
compute_times: [2.1457672119140625e-06]
name: result
compute_fun: <function add_one at 0x7fa91c0896c0>
save_fun: <function save_pickle at 0x7fa8ef285e10>
load_fun: <function load_pickle at 0x7fa8ef285ea0>
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 0x7fa8ef2b8040>
computed_values: [2]
compute_times: [2.1457672119140625e-06]
name: result
compute_fun: <function add_one at 0x7fa91c0896c0>
save_fun: <function save_pickle at 0x7fa8ef285e10>
load_fun: <function load_pickle at 0x7fa8ef285ea0>
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 0x7fa8ef2b8040>
computed_values: [2]
compute_times: [2.1457672119140625e-06]
name: result
compute_fun: <function add_one at 0x7fa91c0896c0>
save_fun: <function save_pickle at 0x7fa8ef285e10>
load_fun: <function load_pickle at 0x7fa8ef285ea0>
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 0x7fa8ef2b8040>
computed_values: [2, 4]
compute_times: [2.1457672119140625e-06, 1.1920928955078125e-06]
name: result
compute_fun: <function add_one at 0x7fa91c0896c0>
save_fun: <function save_pickle at 0x7fa8ef285e10>
load_fun: <function load_pickle at 0x7fa8ef285ea0>
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 0x7fa8ef2b8040>
computed_values: [2, 4]
compute_times: [2.1457672119140625e-06, 1.1920928955078125e-06]
name: result
compute_fun: <function add_one at 0x7fa91c0896c0>
save_fun: <function save_pickle at 0x7fa8ef285e10>
load_fun: <function load_pickle at 0x7fa8ef285ea0>
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 0x7fa8ef2b8040>
computed_values: [2, 4, 6]
compute_times: [2.1457672119140625e-06, 1.1920928955078125e-06, 1...
name: result
compute_fun: <function add_one at 0x7fa91c0896c0>
save_fun: <function save_pickle at 0x7fa8ef285e10>
load_fun: <function load_pickle at 0x7fa8ef285ea0>
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],
[-1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1]],
[[-1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1]]]])
Coordinates:
* letter (letter) object 'c' 'a' 'b'
* constant (constant) int64 4
* date (date) object 2000-01-01 2023-05-04
* parameter (parameter) int64 1 2 3 4 5
Attributes:
tracking_compute_fun: <function add_one at 0x7fa8ef2b8040>
computed_values: [2, 4, 6]
compute_times: [2.1457672119140625e-06, 1.1920928955078125e-06, 1...
name: result
compute_fun: <function add_one at 0x7fa91c0896c0>
save_fun: <function save_pickle at 0x7fa8ef285e10>
load_fun: <function load_pickle at 0x7fa8ef285ea0>
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
<xarray.Dataset>
Dimensions: (letter: 3, constant: 1, date: 2, parameter: 5)
Coordinates:
* letter (letter) object 'c' 'a' 'b'
* constant (constant) int64 4
* date (date) object 2000-01-01 2023-05-04
* parameter (parameter) int64 1 2 3 4 5
Data variables:
result (constant, date, letter, parameter) int64 0 -1 1 ... -1 -1 -1
other_result (constant, date, letter, parameter) int64 -1 -1 -1 ... -1 -1
c (constant, date, letter, parameter) int64 -1 -1 -1 ... -1 -1Saving 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_constant=4_date=2000-01-01_letter=c_parameter=5.pickle.
Computing c for the following parameter values:
{'constant': 4, 'date': datetime.date(2000, 1, 1), 'letter': 'c', '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.