statrl 1.2609__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. statrl/__init__.py +0 -0
  2. statrl/experiments/__init__.py +0 -0
  3. statrl/experiments/analyzeruns.py +95 -0
  4. statrl/experiments/massiveruns.py +124 -0
  5. statrl/experiments/onerun.py +111 -0
  6. statrl/experiments/parallelruns.py +60 -0
  7. statrl/experiments/plotruns.py +144 -0
  8. statrl/experiments/utils.py +140 -0
  9. statrl/settings/__init__.py +0 -0
  10. statrl/settings/bandits/__init__.py +0 -0
  11. statrl/settings/bandits/adversarial/__init__.py +0 -0
  12. statrl/settings/bandits/adversarial/lipschitz/__init__.py +0 -0
  13. statrl/settings/bandits/adversarial/lipschitz/agent.py +53 -0
  14. statrl/settings/bandits/adversarial/lipschitz/agents/ALF.py +202 -0
  15. statrl/settings/bandits/adversarial/lipschitz/agents/__init__.py +0 -0
  16. statrl/settings/bandits/adversarial/lipschitz/environment.py +117 -0
  17. statrl/settings/bandits/adversarial/lipschitz/envs/__init__.py +0 -0
  18. statrl/settings/bandits/adversarial/lipschitz/interaction.py +62 -0
  19. statrl/settings/bandits/adversarial/lipschitz/wrappers/__init__.py +0 -0
  20. statrl/settings/bandits/stochastic/__init__.py +0 -0
  21. statrl/settings/bandits/stochastic/anytime/__init__.py +0 -0
  22. statrl/settings/bandits/stochastic/anytime/_test.py +74 -0
  23. statrl/settings/bandits/stochastic/anytime/agent.py +76 -0
  24. statrl/settings/bandits/stochastic/anytime/agents/IMED.py +156 -0
  25. statrl/settings/bandits/stochastic/anytime/agents/NPTS.py +136 -0
  26. statrl/settings/bandits/stochastic/anytime/agents/TS.py +119 -0
  27. statrl/settings/bandits/stochastic/anytime/agents/UCB.py +112 -0
  28. statrl/settings/bandits/stochastic/anytime/agents/_Oracle.py +66 -0
  29. statrl/settings/bandits/stochastic/anytime/agents/_Random.py +54 -0
  30. statrl/settings/bandits/stochastic/anytime/agents/__init__.py +0 -0
  31. statrl/settings/bandits/stochastic/anytime/environment.py +161 -0
  32. statrl/settings/bandits/stochastic/anytime/envs/__init__.py +0 -0
  33. statrl/settings/bandits/stochastic/anytime/envs/distributions.py +126 -0
  34. statrl/settings/bandits/stochastic/anytime/envs/parametric.py +145 -0
  35. statrl/settings/bandits/stochastic/anytime/interaction.py +110 -0
  36. statrl/settings/bandits/stochastic/anytime/renderers/__init__.py +0 -0
  37. statrl/settings/bandits/stochastic/anytime/renderers/textrenderer.py +69 -0
  38. statrl/settings/bandits/stochastic/anytime/wrappers/__init__.py +0 -0
  39. statrl/settings/bandits/stochastic/batch/__init__.py +0 -0
  40. statrl/settings/bandits/stochastic/batch/_test.py +60 -0
  41. statrl/settings/bandits/stochastic/batch/agent.py +110 -0
  42. statrl/settings/bandits/stochastic/batch/agents/BABA.py +687 -0
  43. statrl/settings/bandits/stochastic/batch/agents/BCB.py +255 -0
  44. statrl/settings/bandits/stochastic/batch/agents/BIMED.py +235 -0
  45. statrl/settings/bandits/stochastic/batch/agents/_Oracle.py +89 -0
  46. statrl/settings/bandits/stochastic/batch/agents/_Random.py +80 -0
  47. statrl/settings/bandits/stochastic/batch/agents/__init__.py +0 -0
  48. statrl/settings/bandits/stochastic/batch/agents/baba_schedule.py +246 -0
  49. statrl/settings/bandits/stochastic/batch/environment.py +121 -0
  50. statrl/settings/bandits/stochastic/batch/envs/__init__.py +0 -0
  51. statrl/settings/bandits/stochastic/batch/envs/parametric.py +206 -0
  52. statrl/settings/bandits/stochastic/batch/envs/renderers/__init__.py +0 -0
  53. statrl/settings/bandits/stochastic/batch/interaction.py +102 -0
  54. statrl/settings/bandits/stochastic/kernel/__init__.py +0 -0
  55. statrl/settings/bandits/stochastic/kernel/_test.py +27 -0
  56. statrl/settings/bandits/stochastic/kernel/agent.py +83 -0
  57. statrl/settings/bandits/stochastic/kernel/agents/_Oracle.py +62 -0
  58. statrl/settings/bandits/stochastic/kernel/agents/_Random.py +39 -0
  59. statrl/settings/bandits/stochastic/kernel/agents/__init__.py +0 -0
  60. statrl/settings/bandits/stochastic/kernel/environment.py +237 -0
  61. statrl/settings/bandits/stochastic/kernel/envs/__init__.py +0 -0
  62. statrl/settings/bandits/stochastic/kernel/envs/kernels.py +175 -0
  63. statrl/settings/bandits/stochastic/kernel/interaction.py +105 -0
  64. statrl/settings/bandits/stochastic/kernel/renderers/__init__.py +0 -0
  65. statrl/settings/bandits/stochastic/kernel/renderers/plotrenderer.py +256 -0
  66. statrl/settings/bandits/stochastic/kernel/renderers/textrenderer.py +100 -0
  67. statrl/settings/bandits/stochastic/knownhorizon/__init__.py +0 -0
  68. statrl/settings/bandits/stochastic/knownhorizon/agent.py +71 -0
  69. statrl/settings/bandits/stochastic/knownhorizon/agents/__init__.py +0 -0
  70. statrl/settings/bandits/stochastic/knownhorizon/environment.py +13 -0
  71. statrl/settings/bandits/stochastic/knownhorizon/envs/__init__.py +0 -0
  72. statrl/settings/bandits/stochastic/knownhorizon/interaction.py +141 -0
  73. statrl/settings/bandits/stochastic/knownhorizon/wrappers/__init__.py +0 -0
  74. statrl/settings/bandits/stochastic/knownhorizon/wrappers/wrapper_anytime_knownhorizon.py +143 -0
  75. statrl/settings/markovdecisionprocess/__init__.py +0 -0
  76. statrl/settings/markovdecisionprocess/discrete_nostructure/__init__.py +0 -0
  77. statrl/settings/markovdecisionprocess/discrete_nostructure/_test.py +68 -0
  78. statrl/settings/markovdecisionprocess/discrete_nostructure/agent.py +82 -0
  79. statrl/settings/markovdecisionprocess/discrete_nostructure/agents/Human.py +87 -0
  80. statrl/settings/markovdecisionprocess/discrete_nostructure/agents/IMED_RL.py +411 -0
  81. statrl/settings/markovdecisionprocess/discrete_nostructure/agents/PSRL.py +356 -0
  82. statrl/settings/markovdecisionprocess/discrete_nostructure/agents/PSRL_original.py +306 -0
  83. statrl/settings/markovdecisionprocess/discrete_nostructure/agents/_Oracle.py +260 -0
  84. statrl/settings/markovdecisionprocess/discrete_nostructure/agents/_Random.py +56 -0
  85. statrl/settings/markovdecisionprocess/discrete_nostructure/agents/__init__.py +0 -0
  86. statrl/settings/markovdecisionprocess/discrete_nostructure/environment.py +250 -0
  87. statrl/settings/markovdecisionprocess/discrete_nostructure/envs/__init__.py +0 -0
  88. statrl/settings/markovdecisionprocess/discrete_nostructure/envs/randomMDP.py +174 -0
  89. statrl/settings/markovdecisionprocess/discrete_nostructure/envs/riverswim.py +207 -0
  90. statrl/settings/markovdecisionprocess/discrete_nostructure/interaction.py +101 -0
  91. statrl/settings/markovdecisionprocess/discrete_nostructure/renderers/__init__.py +0 -0
  92. statrl/settings/markovdecisionprocess/discrete_nostructure/renderers/htmlRenderer.py +1744 -0
  93. statrl/settings/markovdecisionprocess/discrete_nostructure/renderers/textRenderer.py +89 -0
  94. statrl/settings/markovdecisionprocess/discrete_nostructure/wrappers/__init__.py +0 -0
  95. statrl/settings/markovdecisionprocess/gridworld/__init__.py +0 -0
  96. statrl/settings/markovdecisionprocess/gridworld/_test.py +62 -0
  97. statrl/settings/markovdecisionprocess/gridworld/agent.py +3 -0
  98. statrl/settings/markovdecisionprocess/gridworld/agents/_Oracle.py +168 -0
  99. statrl/settings/markovdecisionprocess/gridworld/agents/_Random.py +1 -0
  100. statrl/settings/markovdecisionprocess/gridworld/agents/__init__.py +0 -0
  101. statrl/settings/markovdecisionprocess/gridworld/environment.py +4 -0
  102. statrl/settings/markovdecisionprocess/gridworld/envs/__init__.py +0 -0
  103. statrl/settings/markovdecisionprocess/gridworld/envs/gridworlds.py +905 -0
  104. statrl/settings/markovdecisionprocess/gridworld/interaction.py +102 -0
  105. statrl/settings/markovdecisionprocess/gridworld/renderers/__init__.py +0 -0
  106. statrl/settings/markovdecisionprocess/gridworld/renderers/htmlrenderer.py +114 -0
  107. statrl/settings/markovdecisionprocess/gridworld/renderers/textRenderer.py +184 -0
  108. statrl/settings/utils.py +366 -0
  109. statrl/settings/validator.py +307 -0
  110. statrl-1.2609.dist-info/METADATA +129 -0
  111. statrl-1.2609.dist-info/RECORD +114 -0
  112. statrl-1.2609.dist-info/WHEEL +5 -0
  113. statrl-1.2609.dist-info/licenses/LICENSE +21 -0
  114. statrl-1.2609.dist-info/top_level.txt +1 -0
statrl/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,95 @@
1
+
2
+ import pickle
3
+ import time
4
+ import numpy as np
5
+
6
+ def computeScoreDiffs(names: list[str], dump_scores: list[list[str]], timeHorizon: int, envName: str, root_folder: str) -> tuple[list[np.ndarray], list[np.ndarray], list[np.ndarray],list[np.ndarray],list[np.ndarray], list[np.ndarray], list[int]]:
7
+ """Turn per-replicate score dumps into regret statistics over time.
8
+
9
+ Loads every dump, subtracts each agent's cumulative score from the
10
+ oracle's averaged one to obtain regret, and summarizes the replicates by
11
+ their mean, median, and four quantiles.
12
+
13
+ Parameters
14
+ ----------
15
+ names : list of str
16
+ Agent names, in the same order as ``dump_scores``. Used to name the
17
+ per-agent regret pickles.
18
+ dump_scores : list of list of str
19
+ One list of dump filenames per agent. **The last entry must be the
20
+ oracle's**, and it is what every other entry is compared against — the
21
+ function has no other way to tell which agent is the reference.
22
+ timeHorizon : int
23
+ Number of rounds each run played.
24
+ envName : str
25
+ Environment name, used in the output filenames.
26
+ root_folder : str
27
+ Directory the regret pickles are written to.
28
+
29
+ Returns
30
+ -------
31
+ mean, median : list of ndarray
32
+ Per-agent mean and median regret at each sampled time.
33
+ quantile1, quantile2, quantile3, quantile4 : list of ndarray
34
+ Per-agent regret quantiles at levels 0.1, 0.25, 0.75, and 0.9. The
35
+ plots shade 0.1-0.9 and 0.25-0.75 as nested bands.
36
+ times : list of int
37
+ Sampled time steps, shared by every returned series.
38
+
39
+ Notes
40
+ -----
41
+ Long runs are downsampled to at most ~1000 points
42
+ (``skip = timeHorizon // 1000``), which bounds both plot size and memory.
43
+ Each returned series therefore has ``len(times)`` entries, not
44
+ ``timeHorizon``.
45
+
46
+ The oracle's score is averaged across its replicates *before* the
47
+ subtraction, so the result is regret against mean oracle performance
48
+ rather than a paired difference per replicate.
49
+ """
50
+
51
+ median = []
52
+ mean = []
53
+ quantile1 = []
54
+ quantile2 = []
55
+ quantile3 = []
56
+ quantile4 = []
57
+ nbAlgs = len(dump_scores) - 1
58
+
59
+ #Downsample the times, especially in case timeHorizon is huge.
60
+ skip = max(1, (timeHorizon // 1000))
61
+ times = [t for t in range(0,timeHorizon,skip)]
62
+
63
+ #file_oracle = open(dump_scores[-1], 'rb')
64
+ #scores_oracle = pickle.load(file_oracle)
65
+ # Comment the following line for BatchMabs:
66
+ #scores_oracle = scores_oracle[0]
67
+ #file_oracle.close()
68
+
69
+ data_o = []
70
+ for oracle_file in dump_scores[-1]:
71
+ with open(oracle_file, 'rb') as file:
72
+ scores_oi = pickle.load(file)
73
+ data_o.append([scores_oi[t] for t in times])
74
+ scores_oracle = np.mean(data_o, axis=0)
75
+
76
+ for j in range(nbAlgs):
77
+ data_j = []
78
+ for alg_file in dump_scores[j]:
79
+ with open(alg_file, 'rb') as file:
80
+ scores_ij = pickle.load(file)
81
+ data_j.append([scores_oracle[k] - scores_ij[t] for k, t in enumerate(times)])
82
+
83
+ filename = f"{root_folder}regret_{envName}_{names[j]}_{timeHorizon}_{j}_{time.time()}"
84
+ with open(filename, 'wb') as out_file:
85
+ pickle.dump(data_j, out_file)
86
+
87
+ mean.append(np.mean(data_j, axis=0))
88
+ median.append(np.quantile(data_j, 0.5, axis=0))
89
+ quantile1.append(np.quantile(data_j, 0.1, axis=0))
90
+ quantile2.append(np.quantile(data_j, 0.25, axis=0))
91
+ quantile3.append(np.quantile(data_j, 0.75, axis=0))
92
+ quantile4.append(np.quantile(data_j, 0.9, axis=0))
93
+
94
+ return mean,median,quantile1,quantile2, quantile3, quantile4,times
95
+
@@ -0,0 +1,124 @@
1
+
2
+
3
+ import statrl.experiments.onerun as oR
4
+ import statrl.experiments.parallelruns as pR
5
+ import statrl.experiments.analyzeruns as aR
6
+ import statrl.experiments.plotruns as plR
7
+ from statrl.experiments.utils import clear_auxiliaryfiles
8
+
9
+ import time
10
+ import os
11
+ from typing import Any
12
+ ROOT="results/"
13
+
14
+
15
+ def runLargeMulticoreExperiment(env: Any, agents: list[Any], oracle: Any, interact: Any, timeHorizon: int=1000, nbReplicates: int=100, root_folder: str=ROOT) -> None:
16
+ """Benchmark several agents on one environment and plot their regret.
17
+
18
+ For each agent it runs ``nbReplicates`` independent interactions in parallel, runs the oracle for
19
+ the same number, computes regret as the oracle's cumulative score minus each agent's, and writes
20
+ a logfile and regret figures under ``root_folder``.
21
+
22
+ Parameters
23
+ ----------
24
+ env : object
25
+ Environment to benchmark on. Must expose ``name``; an optional
26
+ ``displayname`` is used as the figure title when present.
27
+ agents : list of object
28
+ Agents to compare. Their ``name`` attributes must be distinct — dump
29
+ filenames and plot legends are keyed on them, so duplicates silently
30
+ merge two agents' results.
31
+ oracle : object
32
+ Reference agent defining zero regret, and the only one required to
33
+ expose a ``policy`` (it is written to the logfile). Must belong to the
34
+ same setting as ``agents``.
35
+ interact : statrl.experiments.onerun.Interaction
36
+ Interaction loop of the setting, shared by every agent in the run.
37
+ timeHorizon : int, default=1000
38
+ Number of rounds per interaction.
39
+ nbReplicates : int, default=100
40
+ Number of independent runs per agent. Regret quantiles are taken
41
+ across these, so a handful of replicates gives a very rough band.
42
+ root_folder : str, default='results/'
43
+ Output directory, created if absent. Must end with a separator.
44
+
45
+ Returns
46
+ -------
47
+ None
48
+ Everything is written to disk. ``root_folder`` receives a
49
+ ``logfile_*.txt``, one ``regret_*`` pickle per agent, and the figures
50
+ ``Regrets_*.png`` / ``.pdf`` in linear and log-y scale. The
51
+ intermediate ``aux_*`` dumps are deleted on the way out.
52
+
53
+ See Also
54
+ --------
55
+ statrl.experiments.parallelruns.multicoreRuns : The parallel layer underneath.
56
+ statrl.experiments.analyzeruns.computeScoreDiffs : Turns the dumps into regret statistics.
57
+ statrl.experiments.plotruns.plotScoreDiffs : Draws the figures.
58
+
59
+ Notes
60
+ -----
61
+ Cost grows as ``(len(agents) + 1) * nbReplicates * timeHorizon``. Start
62
+ small — the defaults already amount to 100 000 rounds per agent.
63
+
64
+ Examples
65
+ --------
66
+ >>> from statrl.settings.bandits.stochastic.anytime.envs.parametric import BernoulliBandit
67
+ >>> from statrl.settings.bandits.stochastic.anytime.agents.IMED import IMED
68
+ >>> from statrl.settings.bandits.stochastic.anytime.agents._Oracle import Oracle
69
+ >>> from statrl.settings.bandits.stochastic.anytime.agents._Random import Random
70
+ >>> from statrl.settings.bandits.stochastic.anytime.interaction import BanditInteraction
71
+ >>> from statrl.settings.utils import klBern
72
+ >>> env = BernoulliBandit([0.2, 0.9, 0.5]) # doctest: +SKIP
73
+ >>> runLargeMulticoreExperiment( # doctest: +SKIP
74
+ ... env,
75
+ ... agents=[IMED(env.number_arms, klBern), Random(env)],
76
+ ... oracle=Oracle(env),
77
+ ... interact=BanditInteraction(),
78
+ ... timeHorizon=1000, nbReplicates=50,
79
+ ... )
80
+ """
81
+ os.makedirs(root_folder, exist_ok=True)
82
+
83
+ envName = env.name
84
+ learners = agents
85
+
86
+ print("-"*30+"Massive Multicore Experiment"+"-"*30)
87
+ print(f'Environment: {envName}')
88
+ print(f'Learners: {[learner.name for learner in learners]}')
89
+ print(f'[INFO] Run {nbReplicates} many interactions of length {timeHorizon} for each learner:')
90
+ dump_scores = []
91
+ names = []
92
+ meanelapsedtimes = []
93
+
94
+ for learner in learners:
95
+ names.append(learner.name)
96
+ dump_scores_learner, meanelapsedtime_learner = pR.multicoreRuns(env, learner, interact, nbReplicates, timeHorizon, oR.oneRunWithDump, root_folder=root_folder)
97
+ dump_scores.append(dump_scores_learner)
98
+ meanelapsedtimes.append(meanelapsedtime_learner)
99
+
100
+ dump_scoresopt, meanelapsedtime = pR.multicoreRuns(env, oracle, interact, nbReplicates, timeHorizon,
101
+ oR.oneRunWithDump, root_folder=root_folder)
102
+ dump_scores.append(dump_scoresopt)
103
+
104
+ ## Report statistics and compute regret:
105
+ timestamp = str(time.time())
106
+ logfilename = f"{root_folder}logfile_{envName}_{timestamp}.txt"
107
+ with open(logfilename, 'w') as logfile:
108
+ logfile.write("Environment " + envName + "\n")
109
+ logfile.write("Optimal policy is: " + str(oracle.policy) + "\n")
110
+ logfile.write("Learners " + str([learner.name for learner in learners]) + "\n")
111
+ logfile.write("Time horizon is " + str(timeHorizon) + ", nb of replicates is " + str(nbReplicates) + "\n")
112
+ for name, meanelapsedtime in zip(names, meanelapsedtimes):
113
+ logfile.write(f"{name} average runtime is {meanelapsedtime}\n")
114
+ print("[INFO] A log-file has been generated in ", logfilename)
115
+ print("[INFO] Compute Statistics...")
116
+ mean, median, quantile1, quantile2,quantile3,quantile4, times = aR.computeScoreDiffs(names, dump_scores, timeHorizon, envName, root_folder=root_folder)
117
+ print("[INFO] Plot results...")
118
+
119
+ title = env.displayname if hasattr(env, "displayname") else envName
120
+ labelx,labely = interact.plotlabels
121
+ plR.plotScoreDiffs(names, envName, (title,labelx,labely), mean, median, quantile1, quantile2,quantile3,quantile4, times, timeHorizon, logfile=logfile, timestamp=timestamp, root_folder=root_folder)
122
+ print("[INFO] Clean Auxiliary files...")
123
+ clear_auxiliaryfiles(env, root_folder)
124
+ print("[INFO] Massive multicore experiment successfully completed.")
@@ -0,0 +1,111 @@
1
+
2
+ from statrl.experiments.utils import dump
3
+
4
+ import time
5
+
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any
8
+ import numpy as np
9
+
10
+ class Interaction(ABC):
11
+ """Base class for the interaction loop of a setting.
12
+ """
13
+
14
+ @abstractmethod
15
+ def run(self, env: Any, learner: Any, horizon: int)-> np.ndarray:
16
+ """Run one interaction and return its cumulative expected score.
17
+
18
+ Parameters
19
+ ----------
20
+ env : object
21
+ Environment of the setting. Reset by the implementation.
22
+ learner : object
23
+ Agent of the setting. Reset by the implementation, so one instance
24
+ can serve many replicates.
25
+ horizon : int
26
+ Number of rounds to play.
27
+
28
+ Returns
29
+ -------
30
+ ndarray of shape (horizon,)
31
+ Cumulative *expected* reward. Implementations must return exactly
32
+ ``horizon`` entries — :func:`oneRunWithDump` asserts it.
33
+ """
34
+ return np.array([])
35
+
36
+
37
+ @abstractmethod
38
+ def renderrun(self, env: Any, learner: Any, horizon: int) -> None:
39
+ """Run one interaction with rendering enabled, returning no score.
40
+
41
+ Parameters
42
+ ----------
43
+ env : object
44
+ Environment of the setting; the implementation attaches renderers.
45
+ learner : object
46
+ Agent of the setting.
47
+ horizon : int
48
+ Number of rounds to play.
49
+ """
50
+ ...
51
+
52
+ @property
53
+ @abstractmethod
54
+ def plotlabels(self) -> tuple[str, str]:
55
+ """tuple of (str, str): Axis labels ``(x, y)`` for the regret plots.
56
+
57
+ Read by
58
+ :func:`~statrl.experiments.massiveruns.runLargeMulticoreExperiment`
59
+ and forwarded to
60
+ :func:`~statrl.experiments.plotruns.plotScoreDiffs`. Settings whose
61
+ rounds are not time steps override it — the batch setting labels its
62
+ x-axis by episode.
63
+ """
64
+ raise NotImplementedError
65
+
66
+
67
+
68
+
69
+ def oneRunWithDump(env: Any, learner: Any, interact: Any, timeHorizon: int, root_folder: str) -> str:
70
+ """Run one replicate and pickle its score series to disk.
71
+
72
+ The unit of work handed to :mod:`joblib` by
73
+ :func:`~statrl.experiments.parallelruns.multicoreRuns`. Results travel
74
+ back through the filesystem rather than through the return value, because
75
+ returning full score series from every worker would serialize
76
+ ``nbReplicates x timeHorizon`` floats through the process pool.
77
+
78
+ Parameters
79
+ ----------
80
+ env : object
81
+ Environment for this replicate; already a private deep copy.
82
+ learner : object
83
+ Agent for this replicate; already a private deep copy.
84
+ interact : Interaction
85
+ The interaction loop of the setting.
86
+ timeHorizon : int
87
+ Number of rounds to play.
88
+ root_folder : str
89
+ Directory the dump is written to. Must end with a separator, and must
90
+ already exist.
91
+
92
+ Returns
93
+ -------
94
+ str
95
+ Path of the pickle written, of the form
96
+ ``{root_folder}aux_{env}_scores_{agent}_{horizon}_{timestamp}``. The
97
+ ``aux_`` prefix is what
98
+ :func:`~statrl.experiments.utils.clear_auxiliaryfiles` deletes once
99
+ the statistics have been computed.
100
+
101
+ Raises
102
+ ------
103
+ AssertionError
104
+ If the interaction returned fewer or more than ``timeHorizon`` scores.
105
+ """
106
+ scoretimeseries=interact.run(env,learner,timeHorizon)
107
+ assert len(scoretimeseries) == timeHorizon
108
+
109
+ tag = f"{env.name}_scores_{learner.name}_{timeHorizon}_{time.time()}"
110
+ filename = dump(scoretimeseries,"aux",tag,root_folder)
111
+ return filename
@@ -0,0 +1,60 @@
1
+ import time
2
+ import copy
3
+ from joblib import Parallel, delayed
4
+ from typing import Any, Callable
5
+
6
+
7
+ ## Parallelization
8
+ def multicoreRuns(env: Any, learner: Any, interact: Any, nbReplicates: int, timeHorizon: int, oneRunFunction: Callable[..., Any], root_folder: str) -> tuple[Any, float]:
9
+ """Run one agent for many independent replicates, spread across CPU cores.
10
+
11
+ Each replicate gets its own deep copy of the environment, agent, and
12
+ interaction.
13
+
14
+ Parameters
15
+ ----------
16
+ env : object
17
+ Environment to replicate.
18
+ learner : object
19
+ Agent to replicate.
20
+ interact : statrl.experiments.onerun.Interaction
21
+ Interaction loop of the setting.
22
+ nbReplicates : int
23
+ Number of independent runs.
24
+ timeHorizon : int
25
+ Number of rounds per run.
26
+ oneRunFunction : callable
27
+ Function executing one replicate, called as
28
+ ``oneRunFunction(env, learner, interact, timeHorizon, root_folder)``.
29
+ In practice :func:`~statrl.experiments.onerun.oneRunWithDump`.
30
+ root_folder : str
31
+ Directory the per-replicate dumps are written to.
32
+
33
+ Returns
34
+ -------
35
+ scores : list of str
36
+ One dump filename per replicate, in the order the jobs were created.
37
+ elapsed : float
38
+ Mean wall-clock seconds per replicate. Since the runs are concurrent
39
+ this is total elapsed time divided by ``nbReplicates``, so it measures
40
+ throughput rather than the cost of a single run.
41
+
42
+ Notes
43
+ -----
44
+ Uses all available cores (``n_jobs=-1``). Everything passed in must be
45
+ picklable, which is why
46
+ :class:`~statrl.settings.bandits.stochastic.batch.environment.BatchMAB` accepts a
47
+ plain list of batch sizes rather than only a callable.
48
+ """
49
+ #FIXME Should be made more general? indep of gymnasium?
50
+ #envs.append(gymnasium.make(envRegisterName).unwrapped)
51
+ jobs = [
52
+ (copy.deepcopy(env), copy.deepcopy(learner), copy.deepcopy(interact), timeHorizon, root_folder)
53
+ for _ in range(nbReplicates)
54
+ ]
55
+
56
+ t0 = time.time()
57
+ scores = Parallel(n_jobs=-1)(delayed(oneRunFunction)(*job) for job in jobs)
58
+ elapsed = time.time() - t0
59
+
60
+ return scores, elapsed / nbReplicates
@@ -0,0 +1,144 @@
1
+
2
+ import pylab as pl
3
+ import sys
4
+ from typing import Any
5
+ import numpy as np
6
+
7
+ ROOT= "results/"
8
+ def plotScoreDiffs(learnersName: list[str], envName: str, title, mean: list[np.ndarray], median: list[np.ndarray], quantile1: list[np.ndarray], quantile2: list[np.ndarray],quantile3: list[np.ndarray],quantile4: list[np.ndarray], times: list[int], timeHorizon: int, logfile: Any='', timestamp: Any=0, root_folder: str=ROOT) -> None:
9
+ """Draw the regret figures and record final regrets in the logfile.
10
+
11
+ Each agent gets a mean curve with markers, a dashed median, and two nested
12
+ shaded bands (0.1-0.9 and 0.25-0.75).
13
+
14
+ Parameters
15
+ ----------
16
+ learnersName : list of str
17
+ Agent names, used as legend labels and in the output filename.
18
+ envName : str
19
+ Environment name, used in the output filename.
20
+ title : tuple of (str, str, str)
21
+ ``(figure_title, xlabel, ylabel)``. The two labels come from
22
+ :attr:`~statrl.experiments.onerun.Interaction.plotlabels`.
23
+ mean, median : list of ndarray
24
+ Per-agent mean and median regret over time.
25
+ quantile1, quantile2, quantile3, quantile4 : list of ndarray
26
+ Per-agent regret quantiles at levels 0.1, 0.25, 0.75, and 0.9.
27
+ times : list of int
28
+ Sampled time steps, shared by every series.
29
+ timeHorizon : int
30
+ Horizon of the runs, used for the x-limit and in the filename.
31
+ logfile : file-like, default=''
32
+ Where final regrets are written. The default empty string selects
33
+ :data:`sys.stdout`.
34
+ timestamp : str, default=0
35
+ Suffix making the filename unique across runs.
36
+ root_folder : str, default='results/'
37
+ Output directory. Must end with a separator.
38
+
39
+ Returns
40
+ -------
41
+ None
42
+ Writes ``Regrets_<agents>_<horizon>_<env>_<timestamp>`` as ``.png``
43
+ and ``.pdf``, plus an ``_ylog`` pair with a logarithmic ``y`` axis.
44
+
45
+ Notes
46
+ -----
47
+ Colours and markers cycle after 9 and 5 agents respectively, so beyond
48
+ that two agents become hard to tell apart.
49
+ """
50
+ if (logfile==''):
51
+ logfile=sys.stdout
52
+ nbFigure = pl.gcf().number+1
53
+ pl.figure(nbFigure)
54
+ fig, ax = pl.subplots(layout="constrained")
55
+ textfile = root_folder+"Regrets_"
56
+ #colors= ['black', 'blue','gray', 'green', 'red']#['black', 'purple', 'blue','cyan','yellow', 'orange', 'red', 'chocolate']
57
+ colors = ['#377eb8', '#ff7f00', '#4daf4a',
58
+ '#f781bf', '#a65628', '#984ea3',
59
+ '#999999', '#e41a1c', '#dede00']
60
+
61
+ style = ['o','v','s','d','<']
62
+ m,M=0,0
63
+
64
+ fig_title,fig_xlabel,fig_ylabel=title
65
+
66
+ ax.set_title(fig_title)
67
+
68
+
69
+ for i in range(len(median)):
70
+ m=min(m,min(quantile1[i]),min(mean[i]))
71
+ M=1.1*max(M,max(quantile4[i]),max(mean[i]))
72
+ ax.fill_between(
73
+ times,
74
+ quantile1[i],
75
+ quantile4[i],
76
+ color=colors[i% len(colors)],
77
+ alpha=0.18,
78
+ linewidth=0
79
+ )
80
+ ax.fill_between(
81
+ times,
82
+ quantile2[i],
83
+ quantile3[i],
84
+ color=colors[i % len(colors)],
85
+ alpha=0.18,
86
+ linewidth=0
87
+ )
88
+ ax.plot(
89
+ times,
90
+ median[i],
91
+ color=colors[i% len(colors)],
92
+ alpha=0.6,
93
+ linewidth=1.8,
94
+ linestyle='--'
95
+ )
96
+ ax.plot(
97
+ times,
98
+ mean[i],
99
+ style[i % len(style)],
100
+ markevery=0.15,
101
+ markersize=8,
102
+ color=colors[i% len(colors)],
103
+ linewidth=2.3,
104
+ linestyle='-',
105
+ label=learnersName[i]
106
+ )
107
+
108
+ #pl.plot(times, mean[i], style[i% len(style)], label=learnersName[i], color=colors[i % len(colors)], linewidth=2.0, linestyle='-.', markevery=0.05)
109
+ #pl.plot(times, median[i], style[i% len(style)], color=colors[i % len(colors)], linewidth=2.0, linestyle='--', markevery=0.05)
110
+ #pl.plot(times,quantile1[i], color=colors[i % len(colors)],linestyle=':',linewidth=0.6)
111
+ #pl.plot(times,quantile2[i], color=colors[i % len(colors)],linestyle=':',linewidth=0.6)
112
+
113
+ textfile += learnersName[i] + "_"
114
+ logfile.write(learnersName[i] + ' has regret ' + str(median[i][-1]) + ' after ' + str(timeHorizon) + ' time steps with quantiles ' +
115
+ str(quantile1[i][-1]) +' and '+ str(quantile2[i][-1])+"\n")
116
+
117
+ textfile+="_"+str(timeHorizon)+"_"+envName+"_"+timestamp
118
+ #fig.tight_layout()
119
+ ax.legend(loc=2)
120
+
121
+ ax.set_xlabel(fig_xlabel, fontsize=13, fontname = "Arial")
122
+ ax.set_ylabel(fig_ylabel, fontsize=13, fontname = "Arial")
123
+
124
+ ax.set_xlim(0,min(timeHorizon,len(mean[0]))-1)
125
+ #pl.xticks(times)
126
+ ax.ticklabel_format(axis='both', useMathText = True, useOffset = True, style='sci', scilimits=(0, 0))
127
+ ax.set_ylim([m,M])
128
+ fig.savefig(textfile+'.png')
129
+ fig.savefig(textfile+ '.pdf')
130
+ # pl.xscale('log')
131
+ # pl.savefig(textfile + '_xlog.png')
132
+ # pl.savefig(textfile + '_xlog.pdf')
133
+ # pl.ylim(1)
134
+ #if(timeHorizon>10):
135
+ ax.set_xscale('linear')
136
+ ax.set_yscale('log')
137
+ ax.set_ylim([max(m,1e-0),max(M,2e-0)])
138
+ fig.savefig(textfile + '_ylog.png')
139
+ fig.savefig(textfile + '_ylog.pdf')
140
+ fig.savefig(root_folder+"Regrets" + str(timeHorizon)+"_"+envName+'.pdf')
141
+ # pl.xscale('log')
142
+ # pl.savefig(textfile + '_loglog.png')
143
+ # pl.savefig(textfile + '_loglog.pdf')
144
+ logfile.write("\nPlots are depicted in files "+textfile + ".pdf/png, etc.")
@@ -0,0 +1,140 @@
1
+ import importlib.util
2
+ import os
3
+ import pickle
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import yaml
8
+
9
+
10
+ def dump(values: Any, filename: str, tag: str, root_folder: str) -> str:
11
+ """Pickle a value to ``{root_folder}{filename}_{tag}``.
12
+
13
+ Parameters
14
+ ----------
15
+ values : object
16
+ Anything picklable; in practice a score time series.
17
+ filename : str
18
+ Filename prefix. ``"aux"`` marks a file
19
+ :func:`clear_auxiliaryfiles` may later delete.
20
+ tag : str
21
+ Suffix identifying the run, built from the environment, agent,
22
+ horizon, and a timestamp.
23
+ root_folder : str
24
+ Output directory. Must end with a separator and already exist.
25
+
26
+ Returns
27
+ -------
28
+ str
29
+ Path of the file written.
30
+ """
31
+ filenameM = f"{root_folder}{filename}_{tag}"
32
+ with open(filenameM, 'wb') as file:
33
+ pickle.dump(values, file)
34
+ return filenameM
35
+
36
+ def clear_auxiliaryfiles(env: Any, root_folder: str) -> None:
37
+ """Delete the intermediate per-replicate dumps of one environment.
38
+
39
+ Removes every file in ``root_folder`` whose name starts with
40
+ ``aux_{env.name}``, i.e. the per-replicate score series, once the regret
41
+ statistics have been computed from them. The regret pickles, logfile, and
42
+ figures are kept.
43
+
44
+ Parameters
45
+ ----------
46
+ env : object
47
+ Environment whose dumps to remove, identified by its ``name``.
48
+ root_folder : str
49
+ Directory to clean. Must end with a separator.
50
+
51
+ Warnings
52
+ --------
53
+ Deletes files unconditionally. Anything of yours in ``root_folder`` named
54
+ ``aux_{env.name}*`` will be removed too.
55
+ """
56
+ for file in os.listdir(root_folder):
57
+ if file.startswith("aux_" + env.name):
58
+ os.remove(root_folder + file)
59
+
60
+ #def load(filename):
61
+ # with open(filename, 'r') as file:
62
+ # return yaml.safe_load(file)
63
+ def load(filename: str) -> dict:
64
+ """Read an ``environments.yaml`` registry.
65
+
66
+ Each entry maps an environment name to a spec with an ``entrypoint`` and
67
+ optional ``kwargs``. The spec's directory is recorded under ``_base_dir``
68
+ so :func:`make` can resolve the entrypoint relative to the YAML file
69
+ rather than the working directory.
70
+
71
+ Parameters
72
+ ----------
73
+ filename : str or path-like
74
+ Path to the YAML registry.
75
+
76
+ Returns
77
+ -------
78
+ dict
79
+ The parsed registry, each spec augmented with ``_base_dir``.
80
+
81
+ See Also
82
+ --------
83
+ make : Instantiates one of the returned specs.
84
+ """
85
+ path = Path(filename)
86
+
87
+ with path.open("r") as f:
88
+ envs = yaml.safe_load(f)
89
+
90
+ for spec in envs.values():
91
+ spec["_base_dir"] = path.parent
92
+
93
+ return envs
94
+
95
+ def make(spec: dict) -> Any:
96
+ """Instantiate an environment from a registry spec.
97
+
98
+ Imports the module named by the spec's ``entrypoint`` from the file it
99
+ sits next to and calls the class it names with the spec's ``kwargs``.
100
+ Loading by file path rather than by import name is what lets a setting's
101
+ ``envs/`` directory be a plain folder, with no package or installation.
102
+
103
+ Parameters
104
+ ----------
105
+ spec : dict
106
+ A registry entry as returned by :func:`load`, with keys
107
+ ``entrypoint`` (``"module:Class"``), ``_base_dir``, and optionally
108
+ ``kwargs`` and ``displayname``.
109
+
110
+ Returns
111
+ -------
112
+ object
113
+ The constructed environment, carrying a ``displayname`` attribute —
114
+ the spec's if given, otherwise its ``name``. That value becomes the
115
+ title of the regret figures.
116
+
117
+ Raises
118
+ ------
119
+ ImportError
120
+ If the module named by the entrypoint cannot be loaded.
121
+
122
+ See Also
123
+ --------
124
+ load : Reads the registry this spec comes from.
125
+ """
126
+ module_name, class_name = spec["entrypoint"].split(":")
127
+ kwargs = spec.get("kwargs", {})
128
+
129
+ module_path = Path(spec["_base_dir"]) / f"{module_name}.py"
130
+
131
+ spec_module = importlib.util.spec_from_file_location(module_name, module_path)
132
+ if spec_module is None or spec_module.loader is None:
133
+ raise ImportError(f"cannot load module {module_name} from {module_path}")
134
+ module = importlib.util.module_from_spec(spec_module)
135
+ spec_module.loader.exec_module(module)
136
+
137
+ cls = getattr(module, class_name)
138
+ env = cls(**kwargs)
139
+ env.displayname = spec["displayname"] if "displayname" in spec else env.name
140
+ return env
File without changes
File without changes
File without changes