Skip to content

Commit d82fbae

Browse files
committed
Merge branch 'feature/fname' into 'master'
make 'fname' a parameter to 'save' and 'load' only Closes #122 See merge request qt/adaptive!133
2 parents 353bebb + 329f447 commit d82fbae

File tree

5 files changed

+53
-92
lines changed

5 files changed

+53
-92
lines changed

adaptive/learner/balancing_learner.py

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
from collections import defaultdict
2+
from collections import defaultdict, Iterable
33
from contextlib import suppress
44
from functools import partial
55
from operator import itemgetter
@@ -317,70 +317,67 @@ def from_product(cls, f, learner_type, learner_kwargs, combos):
317317
learners.append(learner)
318318
return cls(learners, cdims=arguments)
319319

320-
def save(self, folder, compress=True):
320+
def save(self, fname, compress=True):
321321
"""Save the data of the child learners into pickle files
322322
in a directory.
323323
324324
Parameters
325325
----------
326-
folder : str
327-
Directory in which the learners's data will be saved.
326+
fname: callable or sequence of strings
327+
Given a learner, returns a filename into which to save the data.
328+
Or a list (or iterable) with filenames.
328329
compress : bool, default True
329330
Compress the data upon saving using `gzip`. When saving
330331
using compression, one must load it with compression too.
331332
332-
Notes
333-
-----
334-
The child learners need to have a 'fname' attribute in order to use
335-
this method.
336-
337333
Example
338334
-------
339-
>>> def combo_fname(val):
340-
... return '__'.join([f'{k}_{v}.p' for k, v in val.items()])
341-
...
342-
... def f(x, a, b): return a * x**2 + b
343-
...
344-
>>> learners = []
345-
>>> for combo in adaptive.utils.named_product(a=[1, 2], b=[1]):
346-
... l = Learner1D(functools.partial(f, combo=combo))
347-
... l.fname = combo_fname(combo) # 'a_1__b_1.p', 'a_2__b_1.p' etc.
348-
... learners.append(l)
349-
... learner = BalancingLearner(learners)
350-
... # Run the learner
351-
... runner = adaptive.Runner(learner)
352-
... # Then save
353-
... learner.save('data_folder') # use 'load' in the same way
335+
>>> def combo_fname(learner):
336+
... val = learner.function.keywords # because functools.partial
337+
... fname = '__'.join([f'{k}_{v}.pickle' for k, v in val])
338+
... return 'data_folder/' + fname
339+
>>>
340+
>>> def f(x, a, b): return a * x**2 + b
341+
>>>
342+
>>> learners = [Learner1D(functools.partial(f, **combo), (-1, 1))
343+
... for combo in adaptive.utils.named_product(a=[1, 2], b=[1]]
344+
>>>
345+
>>> learner = BalancingLearner(learners)
346+
>>> # Run the learner
347+
>>> runner = adaptive.Runner(learner)
348+
>>> # Then save
349+
>>> learner.save(combo_fname) # use 'load' in the same way
354350
"""
355-
if len(self.learners) != len(set(l.fname for l in self.learners)):
356-
raise RuntimeError("The 'learner.fname's are not all unique.")
357-
358-
for l in self.learners:
359-
l.save(os.path.join(folder, l.fname), compress=compress)
351+
if isinstance(fname, Iterable):
352+
for l, _fname in zip(fname, self.learners):
353+
l.save(_fname, compress=compress)
354+
else:
355+
for l in self.learners:
356+
l.save(fname(l), compress=compress)
360357

361-
def load(self, folder, compress=True):
358+
def load(self, fname, compress=True):
362359
"""Load the data of the child learners from pickle files
363360
in a directory.
364361
365362
Parameters
366363
----------
367-
folder : str
368-
Directory from which the learners's data will be loaded.
364+
fname: callable or sequence of strings
365+
Given a learner, returns a filename from which to load the data.
366+
Or a list (or iterable) with filenames.
369367
compress : bool, default True
370368
If the data is compressed when saved, one must load it
371369
with compression too.
372370
373-
Notes
374-
-----
375-
The child learners need to have a 'fname' attribute in order to use
376-
this method.
377-
378371
Example
379372
-------
380373
See the example in the `BalancingLearner.save` doc-string.
381374
"""
382-
for l in self.learners:
383-
l.load(os.path.join(folder, l.fname), compress=compress)
375+
if isinstance(fname, Iterable):
376+
for l, _fname in zip(fname, self.learners):
377+
l.load(_fname, compress=compress)
378+
else:
379+
for l in self.learners:
380+
l.load(fname(l), compress=compress)
384381

385382
def _get_data(self):
386383
return [l._get_data() for l in learner.learners]

adaptive/learner/base_learner.py

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -107,48 +107,31 @@ def copy_from(self, other):
107107
"""
108108
self._set_data(other._get_data())
109109

110-
def save(self, fname=None, compress=True):
110+
def save(self, fname, compress=True):
111111
"""Save the data of the learner into a pickle file.
112112
113113
Parameters
114114
----------
115-
fname : str, optional
116-
The filename of the learner's pickle data file. If None use
117-
the 'fname' attribute, like 'learner.fname = "example.p".
115+
fname : str
116+
The filename into which to save the learner's data.
118117
compress : bool, default True
119118
Compress the data upon saving using 'gzip'. When saving
120119
using compression, one must load it with compression too.
121-
122-
Notes
123-
-----
124-
There are **two ways** of naming the files:
125-
126-
1. Using the ``fname`` argument in ``learner.save(fname='example.p')``
127-
2. Setting the ``fname`` attribute, like
128-
``learner.fname = "data/example.p"`` and then ``learner.save()``.
129120
"""
130-
fname = fname or self.fname
131121
data = self._get_data()
132122
save(fname, data, compress)
133123

134-
def load(self, fname=None, compress=True):
124+
def load(self, fname, compress=True):
135125
"""Load the data of a learner from a pickle file.
136126
137127
Parameters
138128
----------
139-
fname : str, optional
140-
The filename of the saved learner's pickled data file.
141-
If None use the 'fname' attribute, like
142-
'learner.fname = "example.p".
129+
fname : str
130+
The filename from which to load the learner's data.
143131
compress : bool, default True
144132
If the data is compressed when saved, one must load it
145133
with compression too.
146-
147-
Notes
148-
-----
149-
See the notes in the `save` doc-string.
150134
"""
151-
fname = fname or self.fname
152135
with suppress(FileNotFoundError, EOFError):
153136
data = load(fname, compress)
154137
self._set_data(data)
@@ -158,19 +141,3 @@ def __getstate__(self):
158141

159142
def __setstate__(self, state):
160143
self.__dict__ = state
161-
162-
@property
163-
def fname(self):
164-
"""Filename for the learner when it is saved (or loaded) using
165-
`~adaptive.BaseLearner.save` (or `~adaptive.BaseLearner.load` ).
166-
"""
167-
# This is a property because then it will be availible in the DataSaver
168-
try:
169-
return self._fname
170-
except AttributeError:
171-
raise AttributeError("Set 'learner.fname' or use the 'fname'"
172-
" argument when using 'learner.save' or 'learner.load'.")
173-
174-
@fname.setter
175-
def fname(self, fname):
176-
self._fname = fname

adaptive/learner/data_saver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ def _set_data(self, data):
5353
self.learner._set_data(learner_data)
5454

5555
@copy_docstring_from(BaseLearner.save)
56-
def save(self, fname=None, compress=True):
56+
def save(self, fname, compress=True):
5757
# We copy this method because the 'DataSaver' is not a
5858
# subclass of the 'BaseLearner'.
5959
BaseLearner.save(self, fname, compress)
6060

6161
@copy_docstring_from(BaseLearner.load)
62-
def load(self, fname=None, compress=True):
62+
def load(self, fname, compress=True):
6363
# We copy this method because the 'DataSaver' is not a
6464
# subclass of the 'BaseLearner'.
6565
BaseLearner.load(self, fname, compress)

adaptive/tests/test_learners.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -430,15 +430,15 @@ def test_saving_of_balancing_learner(learner_type, f, learner_kwargs):
430430
learner = BalancingLearner([learner_type(f, **learner_kwargs)])
431431
control = BalancingLearner([learner_type(f, **learner_kwargs)])
432432

433-
# set fnames
434-
learner.learners[0].fname = 'test'
435-
control.learners[0].fname = 'test'
436-
437433
simple(learner, lambda l: l.learners[0].npoints > 100)
438434
folder = tempfile.mkdtemp()
435+
436+
def fname(learner):
437+
return folder + 'test'
438+
439439
try:
440-
learner.save(folder=folder)
441-
control.load(folder=folder)
440+
learner.save(fname)
441+
control.load(fname)
442442
if learner_type is not Learner1D:
443443
# Because different scales result in differnt losses
444444
np.testing.assert_almost_equal(learner.loss(), control.loss())

docs/source/tutorial/tutorial.advanced-topics.rst

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,10 @@ Saving and loading learners
3333
Every learner has a `~adaptive.BaseLearner.save` and `~adaptive.BaseLearner.load`
3434
method that can be used to save and load **only** the data of a learner.
3535

36-
There are **two ways** of naming the files: 1. Using the ``fname``
37-
argument in ``learner.save(fname=...)`` 2. Setting the ``fname``
38-
attribute, like ``learner.fname = 'data/example.p`` and then
39-
``learner.save()``
36+
Use the ``fname`` argument in ``learner.save(fname=...)``.
4037

41-
The second way *must be used* when saving the ``learner``\s of a
42-
`~adaptive.BalancingLearner`.
38+
Or, when using a `~adaptive.BalancingLearner` one can use either a callable
39+
that takes the child learner and returns a filename **or** a list of filenames.
4340

4441
By default the resulting pickle files are compressed, to turn this off
4542
use ``learner.save(fname=..., compress=False)``

0 commit comments

Comments
 (0)