import operator import pickle import random import flaky import pytest from adaptive.learner import ( AverageLearner, BalancingLearner, DataSaver, IntegratorLearner, Learner1D, Learner2D, LearnerND, SequenceLearner, ) from adaptive.runner import simple try: import cloudpickle with_cloudpickle = True except ModuleNotFoundError: with_cloudpickle = False try: import dill with_dill = True except ModuleNotFoundError: with_dill = False def goal_1(learner): return learner.npoints >= 10 def goal_2(learner): return learner.npoints >= 20 learners_pairs = [ (Learner1D, dict(bounds=(-1, 1))), (Learner2D, dict(bounds=[(-1, 1), (-1, 1)])), (LearnerND, dict(bounds=[(-1, 1), (-1, 1), (-1, 1)])), (SequenceLearner, dict(sequence=list(range(100)))), (IntegratorLearner, dict(bounds=(0, 1), tol=1e-3)), (AverageLearner, dict(atol=0.1)), ] serializers = [pickle] if with_cloudpickle: serializers.append(cloudpickle) if with_dill: serializers.append(dill) learners = [ (learner_type, learner_kwargs, serializer) for serializer in serializers for learner_type, learner_kwargs in learners_pairs ] def f_for_pickle(x): return 1 def f_for_pickle_datasaver(x): return dict(x=x, y=x) @flaky.flaky(max_runs=3) @pytest.mark.parametrize( "learner_type, learner_kwargs, serializer", learners, ) def test_serialization_for(learner_type, learner_kwargs, serializer): """Test serializing a learner using different serializers.""" def f(x): return random.random() if serializer is pickle: # f from the local scope cannot be pickled f = f_for_pickle # noqa: F811 learner = learner_type(f, **learner_kwargs) simple(learner, goal_1) learner_bytes = serializer.dumps(learner) loss = learner.loss() asked = learner.ask(1) if serializer is not pickle: # With pickle the functions are only pickled by reference del f del learner learner_loaded = serializer.loads(learner_bytes) assert learner_loaded.npoints >= 10 assert loss == learner_loaded.loss() if learner_type is not Learner2D: # cannot test this for Learner2D because # xfailing test_point_adding_order_is_irrelevant assert asked == learner_loaded.ask(1) # load again to undo the ask learner_loaded = serializer.loads(learner_bytes) simple(learner_loaded, goal_2) assert learner_loaded.npoints >= 20 @pytest.mark.parametrize( "serializer", serializers, ) def test_serialization_for_datasaver(serializer): def f(x): return dict(x=1, y=x ** 2) if serializer is pickle: # f from the local scope cannot be pickled f = f_for_pickle_datasaver # noqa: F811 _learner = Learner1D(f, bounds=(-1, 1)) learner = DataSaver(_learner, arg_picker=operator.itemgetter("y")) simple(learner, goal_1) learner_bytes = serializer.dumps(learner) if serializer is not pickle: # With pickle the functions are only pickled by reference del f del _learner del learner learner_loaded = serializer.loads(learner_bytes) assert learner_loaded.npoints >= 10 simple(learner_loaded, goal_2) assert learner_loaded.npoints >= 20 @pytest.mark.parametrize( "serializer", serializers, ) def test_serialization_for_balancing_learner(serializer): def f(x): return x ** 2 if serializer is pickle: # f from the local scope cannot be pickled f = f_for_pickle # noqa: F811 learner_1 = Learner1D(f, bounds=(-1, 1)) learner_2 = Learner1D(f, bounds=(-2, 2)) learner = BalancingLearner([learner_1, learner_2]) simple(learner, goal_1) learner_bytes = serializer.dumps(learner) if serializer is not pickle: # With pickle the functions are only pickled by reference del f del learner_1 del learner_2 del learner learner_loaded = serializer.loads(learner_bytes) assert learner_loaded.npoints >= 10 simple(learner_loaded, goal_2) assert learner_loaded.npoints >= 20