flowMC 0.2.4__tar.gz → 0.3.0__tar.gz

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 (37) hide show
  1. {flowMC-0.2.4/src/flowMC.egg-info → flowMC-0.3.0}/PKG-INFO +39 -3
  2. {flowMC-0.2.4 → flowMC-0.3.0}/README.md +36 -1
  3. {flowMC-0.2.4 → flowMC-0.3.0}/setup.cfg +3 -2
  4. flowMC-0.3.0/src/flowMC/Sampler.py +382 -0
  5. flowMC-0.3.0/src/flowMC/nfmodel/base.py +234 -0
  6. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC/nfmodel/common.py +68 -49
  7. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC/nfmodel/realNVP.py +76 -63
  8. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC/nfmodel/rqSpline.py +187 -132
  9. {flowMC-0.2.4/src/flowMC/sampler → flowMC-0.3.0/src/flowMC/proposal}/Gaussian_random_walk.py +21 -19
  10. {flowMC-0.2.4/src/flowMC/sampler → flowMC-0.3.0/src/flowMC/proposal}/HMC.py +64 -52
  11. {flowMC-0.2.4/src/flowMC/sampler → flowMC-0.3.0/src/flowMC/proposal}/MALA.py +42 -28
  12. {flowMC-0.2.4/src/flowMC/sampler → flowMC-0.3.0/src/flowMC/proposal}/NF_proposal.py +35 -23
  13. flowMC-0.3.0/src/flowMC/proposal/base.py +153 -0
  14. {flowMC-0.2.4/src/flowMC/sampler → flowMC-0.3.0/src/flowMC/proposal}/flowHMC.py +11 -5
  15. flowMC-0.3.0/src/flowMC/strategy/base.py +36 -0
  16. flowMC-0.3.0/src/flowMC/strategy/global_tuning.py +309 -0
  17. flowMC-0.3.0/src/flowMC/strategy/importance_sampling.py +0 -0
  18. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC/utils/EvolutionaryOptimizer.py +33 -12
  19. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC/utils/PythonFunctionWrap.py +20 -8
  20. flowMC-0.3.0/src/flowMC/utils/__init__.py +0 -0
  21. flowMC-0.3.0/src/flowMC/utils/postprocessing.py +61 -0
  22. {flowMC-0.2.4 → flowMC-0.3.0/src/flowMC.egg-info}/PKG-INFO +39 -3
  23. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC.egg-info/SOURCES.txt +14 -11
  24. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC.egg-info/requires.txt +1 -0
  25. flowMC-0.2.4/src/flowMC/nfmodel/base.py +0 -115
  26. flowMC-0.2.4/src/flowMC/nfmodel/utils.py +0 -112
  27. flowMC-0.2.4/src/flowMC/sampler/Proposal_Base.py +0 -111
  28. flowMC-0.2.4/src/flowMC/sampler/Sampler.py +0 -499
  29. flowMC-0.2.4/src/flowMC/utils/PRNG_keys.py +0 -26
  30. {flowMC-0.2.4 → flowMC-0.3.0}/LICENSE +0 -0
  31. {flowMC-0.2.4 → flowMC-0.3.0}/pyproject.toml +0 -0
  32. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC/__init__.py +0 -0
  33. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC/nfmodel/__init__.py +0 -0
  34. {flowMC-0.2.4/src/flowMC/sampler → flowMC-0.3.0/src/flowMC/proposal}/__init__.py +0 -0
  35. {flowMC-0.2.4/src/flowMC/utils → flowMC-0.3.0/src/flowMC/strategy}/__init__.py +0 -0
  36. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC.egg-info/dependency_links.txt +0 -0
  37. {flowMC-0.2.4 → flowMC-0.3.0}/src/flowMC.egg-info/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flowMC
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Normalizing flow exhanced sampler in jax
5
5
  Home-page: https://github.com/kazewong/flowMC
6
6
  Author: Kaze Wong, Marylou Gabrié, Dan Foreman-Mackey
7
7
  Author-email: kazewong.physics@gmail.com
8
8
  License: MIT
9
9
  Keywords: sampling,inference,machine learning,normalizing,autodiff,jax
10
- Requires-Python: >=3.9
10
+ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: jax>=0.4.12
@@ -16,6 +16,7 @@ Requires-Dist: equinox>=0.10.6
16
16
  Requires-Dist: optax>=0.1.5
17
17
  Requires-Dist: evosax>=0.1.4
18
18
  Requires-Dist: tqdm
19
+ Requires-Dist: corner
19
20
 
20
21
  # flowMC
21
22
 
@@ -84,7 +85,42 @@ The test suite is based on pytest. To run the tests, one needs to install `pytes
84
85
 
85
86
  # Attribution
86
87
 
87
- A Jax implementation of methods described in:
88
+ If you used `flowMC` in your research, we would really appericiate it if you could at least cite the following papers:
89
+
90
+ ```
91
+ @article{Wong:2022xvh,
92
+ author = "Wong, Kaze W. k. and Gabri\'e, Marylou and Foreman-Mackey, Daniel",
93
+ title = "{flowMC: Normalizing flow enhanced sampling package for probabilistic inference in JAX}",
94
+ eprint = "2211.06397",
95
+ archivePrefix = "arXiv",
96
+ primaryClass = "astro-ph.IM",
97
+ doi = "10.21105/joss.05021",
98
+ journal = "J. Open Source Softw.",
99
+ volume = "8",
100
+ number = "83",
101
+ pages = "5021",
102
+ year = "2023"
103
+ }
104
+
105
+ @article{Gabrie:2021tlu,
106
+ author = "Gabri\'e, Marylou and Rotskoff, Grant M. and Vanden-Eijnden, Eric",
107
+ title = "{Adaptive Monte Carlo augmented with normalizing flows}",
108
+ eprint = "2105.12603",
109
+ archivePrefix = "arXiv",
110
+ primaryClass = "physics.data-an",
111
+ doi = "10.1073/pnas.2109420119",
112
+ journal = "Proc. Nat. Acad. Sci.",
113
+ volume = "119",
114
+ number = "10",
115
+ pages = "e2109420119",
116
+ year = "2022"
117
+ }
118
+ ```
119
+
120
+ This will help `flowMC` getting more recognition, and the main benefit *for you* is this means the `flowMC` community will grow and it will be continuously improved. If you believe in the magic of open-source software, please support us by attributing our software in your work.
121
+
122
+
123
+ `flowMC` is a Jax implementation of methods described in:
88
124
  > *Efficient Bayesian Sampling Using Normalizing Flows to Assist Markov Chain Monte Carlo Methods* Gabrié M., Rotskoff G. M., Vanden-Eijnden E. - ICML INNF+ workshop 2021 - [pdf](https://openreview.net/pdf?id=mvtooHbjOwx)
89
125
 
90
126
  > *Adaptive Monte Carlo augmented with normalizing flows.*
@@ -65,7 +65,42 @@ The test suite is based on pytest. To run the tests, one needs to install `pytes
65
65
 
66
66
  # Attribution
67
67
 
68
- A Jax implementation of methods described in:
68
+ If you used `flowMC` in your research, we would really appericiate it if you could at least cite the following papers:
69
+
70
+ ```
71
+ @article{Wong:2022xvh,
72
+ author = "Wong, Kaze W. k. and Gabri\'e, Marylou and Foreman-Mackey, Daniel",
73
+ title = "{flowMC: Normalizing flow enhanced sampling package for probabilistic inference in JAX}",
74
+ eprint = "2211.06397",
75
+ archivePrefix = "arXiv",
76
+ primaryClass = "astro-ph.IM",
77
+ doi = "10.21105/joss.05021",
78
+ journal = "J. Open Source Softw.",
79
+ volume = "8",
80
+ number = "83",
81
+ pages = "5021",
82
+ year = "2023"
83
+ }
84
+
85
+ @article{Gabrie:2021tlu,
86
+ author = "Gabri\'e, Marylou and Rotskoff, Grant M. and Vanden-Eijnden, Eric",
87
+ title = "{Adaptive Monte Carlo augmented with normalizing flows}",
88
+ eprint = "2105.12603",
89
+ archivePrefix = "arXiv",
90
+ primaryClass = "physics.data-an",
91
+ doi = "10.1073/pnas.2109420119",
92
+ journal = "Proc. Nat. Acad. Sci.",
93
+ volume = "119",
94
+ number = "10",
95
+ pages = "e2109420119",
96
+ year = "2022"
97
+ }
98
+ ```
99
+
100
+ This will help `flowMC` getting more recognition, and the main benefit *for you* is this means the `flowMC` community will grow and it will be continuously improved. If you believe in the magic of open-source software, please support us by attributing our software in your work.
101
+
102
+
103
+ `flowMC` is a Jax implementation of methods described in:
69
104
  > *Efficient Bayesian Sampling Using Normalizing Flows to Assist Markov Chain Monte Carlo Methods* Gabrié M., Rotskoff G. M., Vanden-Eijnden E. - ICML INNF+ workshop 2021 - [pdf](https://openreview.net/pdf?id=mvtooHbjOwx)
70
105
 
71
106
  > *Adaptive Monte Carlo augmented with normalizing flows.*
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = flowMC
3
- version = 0.2.4
3
+ version = 0.3.0
4
4
  author = Kaze Wong, Marylou Gabrié, Dan Foreman-Mackey
5
5
  author_email = kazewong.physics@gmail.com
6
6
  url = https://github.com/kazewong/flowMC
@@ -21,7 +21,8 @@ install_requires =
21
21
  optax>=0.1.5
22
22
  evosax>=0.1.4
23
23
  tqdm
24
- python_requires = >=3.9
24
+ corner
25
+ python_requires = >=3.10
25
26
 
26
27
  [options.packages.find]
27
28
  where = src
@@ -0,0 +1,382 @@
1
+ import pickle
2
+ import jax
3
+ import jax.numpy as jnp
4
+ from jaxtyping import Array, Int, Float, PRNGKeyArray
5
+ from tqdm import tqdm
6
+ import equinox as eqx
7
+ import optax
8
+ from flowMC.proposal.NF_proposal import NFProposal
9
+ from flowMC.proposal.base import ProposalBase
10
+ from flowMC.nfmodel.base import NFModel
11
+ from flowMC.strategy.base import Strategy
12
+ from flowMC.strategy.global_tuning import GlobalTuning, GlobalSampling
13
+
14
+
15
+ class Sampler:
16
+ """
17
+ Sampler class that host configuration parameters, NF model, and local sampler
18
+
19
+ Args:
20
+ n_dim (int): Dimension of the problem.
21
+ rng_key (PRNGKeyArray): Jax PRNGKey.
22
+ data (dict): Data to be passed to the logpdf function.
23
+ local_sampler (ProposalBase): Local sampler.
24
+ nf_model (NFModel): Normalizing flow model.
25
+
26
+ Keyword Args:
27
+ n_chains (int): Number of chains.
28
+ n_local_steps (int): Number of local steps.
29
+ n_global_steps (int): Number of global steps.
30
+ n_loop_training (int): Number of training loops.
31
+ n_loop_production (int): Number of production loops.
32
+ train_thinning (int): Thinning parameter for training.
33
+ output_thinning (int): Thinning parameter for sampling.
34
+
35
+ use_global (bool): Whether to use the global sampler.
36
+ batch_size (int): Batch size for training.
37
+ n_epochs (int): Number of epochs per training loop
38
+ learning_rate (float): Learning rate of the optimizer.
39
+ momentum (float): Momentum of the optimizer.
40
+ n_max_examples (int): Maximum number of examples per training step.
41
+ n_flow_sample (int): Number of samples to generate from the normalizing flow.
42
+
43
+ precompile (bool): Whether to precompile the local sampler.
44
+ verbose (bool): Whether to print verbose output.
45
+ logging (bool): Whether to log the output.
46
+ outdir (str): Output directory.
47
+ """
48
+
49
+ # Essential parameters
50
+ n_dim: int
51
+ rng_key: PRNGKeyArray
52
+ data: dict
53
+ local_sampler: ProposalBase
54
+ strategies: list[Strategy]
55
+
56
+ # Sampling hyperparameters
57
+ n_chains: int = 20
58
+ n_local_steps: int = 50
59
+ n_global_steps: int = 50
60
+ n_loop_training: int = 3
61
+ n_loop_production: int = 3
62
+ train_thinning: int = 1
63
+ output_thinning: int = 1
64
+ local_autotune: bool = False
65
+
66
+ # Normalizing flow hyperparameters
67
+ global_sampler: NFProposal
68
+ use_global: bool = True
69
+ batch_size: int = 10000
70
+ n_epochs: int = 30
71
+ learning_rate: float = 0.001
72
+ momentum: float = 0.9
73
+ n_max_examples: int = 10000
74
+ n_flow_sample: int = 10000
75
+
76
+ # Logging hyperparameters
77
+ precompile: bool = False
78
+ verbose: bool = False
79
+ logging: bool = True
80
+ outdir: str = "./outdir/"
81
+
82
+ @property
83
+ def nf_model(self):
84
+ return self.global_sampler.model
85
+
86
+ def __init__(
87
+ self,
88
+ n_dim: int,
89
+ rng_key: PRNGKeyArray,
90
+ data: dict,
91
+ local_sampler: ProposalBase,
92
+ nf_model: NFModel,
93
+ **kwargs,
94
+ ):
95
+ # Copying input into the model
96
+
97
+ self.n_dim = n_dim
98
+ self.rng_key = rng_key
99
+ self.data = data
100
+ self.local_sampler = local_sampler
101
+
102
+ # Set and override any given hyperparameters
103
+ class_keys = list(self.__class__.__dict__.keys())
104
+ for key, value in kwargs.items():
105
+ if key in class_keys:
106
+ if not key.startswith("__"):
107
+ setattr(self, key, value)
108
+
109
+ # Initialized local and global samplers
110
+
111
+ self.local_sampler = local_sampler
112
+ if self.precompile:
113
+ self.local_sampler.precompilation(
114
+ n_chains=self.n_chains,
115
+ n_dims=n_dim,
116
+ n_step=self.n_local_steps,
117
+ data=data,
118
+ )
119
+
120
+ self.global_sampler = NFProposal(
121
+ self.local_sampler.logpdf,
122
+ jit=self.local_sampler.jit,
123
+ model=nf_model,
124
+ n_flow_sample=self.n_flow_sample,
125
+ )
126
+
127
+ self.likelihood_vec = self.local_sampler.logpdf_vmap
128
+
129
+ self.optim = optax.chain(
130
+ optax.clip(1.0), optax.adam(self.learning_rate, self.momentum)
131
+ )
132
+ self.optim_state = self.optim.init(eqx.filter(self.nf_model, eqx.is_array))
133
+
134
+ self.strategies = [
135
+ GlobalTuning(
136
+ n_dim=self.n_dim,
137
+ n_chains=self.n_chains,
138
+ n_local_steps=self.n_local_steps,
139
+ n_global_steps=self.n_global_steps,
140
+ n_loop=self.n_loop_training,
141
+ output_thinning=self.output_thinning,
142
+ train_thinning=self.train_thinning,
143
+ optim=self.optim,
144
+ optim_state=self.optim_state,
145
+ n_epochs=self.n_epochs,
146
+ batch_size=self.batch_size,
147
+ n_max_examples=self.n_max_examples,
148
+ verbose=self.verbose,
149
+ ),
150
+ GlobalSampling(
151
+ n_dim=self.n_dim,
152
+ n_chains=self.n_chains,
153
+ n_local_steps=self.n_local_steps,
154
+ n_global_steps=self.n_global_steps,
155
+ n_loop=self.n_loop_production,
156
+ output_thinning=self.output_thinning,
157
+ verbose=self.verbose,
158
+ ),
159
+ ]
160
+
161
+ if kwargs.get("strategies") is not None:
162
+ kwargs_strategies = kwargs.get("strategies")
163
+ assert isinstance(kwargs_strategies, list)
164
+ self.strategies = kwargs_strategies
165
+
166
+ self.summary = {}
167
+
168
+ def sample(self, initial_position: Float[Array, "n_chains n_dim"], data: dict):
169
+ """
170
+ Sample from the posterior using the local sampler.
171
+
172
+ Args:
173
+ initial_position (Device Array): Initial position.
174
+
175
+ Returns:
176
+ chains (Device Array): Samples from the posterior.
177
+ nf_samples (Device Array): (n_nf_samples, n_dim)
178
+ local_accs (Device Array): (n_chains, n_local_steps * n_loop)
179
+ global_accs (Device Array): (n_chains, n_global_steps * n_loop)
180
+ loss_vals (Device Array): (n_epoch * n_loop,)
181
+ """
182
+
183
+ # Note that auto-tune function needs to have the same number of steps
184
+ # as the actual sampling loop to avoid recompilation.
185
+
186
+ initial_position = jnp.atleast_2d(initial_position) # type: ignore
187
+ rng_key = self.rng_key
188
+ last_step = initial_position
189
+ for strategy in self.strategies:
190
+ (
191
+ rng_key,
192
+ last_step,
193
+ self.local_sampler,
194
+ self.global_sampler,
195
+ summary,
196
+ ) = strategy(
197
+ rng_key, self.local_sampler, self.global_sampler, last_step, data
198
+ )
199
+ self.summary[strategy.__name__] = summary
200
+
201
+ def get_sampler_state(self, training: bool = False) -> dict:
202
+ """
203
+ Get the sampler state. There are two sets of sampler outputs one can get,
204
+ the training set and the production set.
205
+ The training set is produced during the global tuning step, and the production set
206
+ is produced during the production run.
207
+ Only the training set contains information about the loss function.
208
+ Only the production set should be used to represent the final set of samples.
209
+
210
+ Args:
211
+ training (bool): Whether to get the training set sampler state. Defaults to False.
212
+
213
+ """
214
+ if training is True:
215
+ return self.summary["GlobalTuning"]
216
+ else:
217
+ return self.summary["GlobalSampling"]
218
+
219
+ def sample_flow(
220
+ self, rng_key: PRNGKeyArray, n_samples: int
221
+ ) -> Float[Array, "n_samples n_dim"]:
222
+ """
223
+ Sample from the normalizing flow.
224
+
225
+ Args:
226
+ n_samples (int): Number of samples to generate.
227
+
228
+ Returns:
229
+ Device Array: Samples generated using the normalizing flow.
230
+ """
231
+
232
+ samples = self.nf_model.sample(rng_key, n_samples)
233
+ return samples
234
+
235
+ def evalulate_flow(
236
+ self, samples: Float[Array, "n_samples n_dim"]
237
+ ) -> Float[Array, "n_samples"]:
238
+ """
239
+ Evaluate the log probability of the normalizing flow.
240
+
241
+ Args:
242
+ samples (Device Array): Samples to evaluate.
243
+
244
+ Returns:
245
+ Device Array: Log probability of the samples.
246
+ """
247
+ log_prob = self.nf_model.log_prob(samples)
248
+ return log_prob
249
+
250
+ def save_flow(self, path: str):
251
+ """
252
+ Save the normalizing flow to a file.
253
+
254
+ Args:
255
+ path (str): Path to save the normalizing flow.
256
+ """
257
+ self.nf_model.save_model(path)
258
+
259
+ def load_flow(self, path: str):
260
+ """
261
+ Save the normalizing flow to a file.
262
+
263
+ Args:
264
+ path (str): Path to save the normalizing flow.
265
+ """
266
+ self.nf_model.load_model(path)
267
+
268
+ def reset(self):
269
+ """
270
+ Reset the sampler state.
271
+
272
+ """
273
+ self.summary = {}
274
+
275
+ def get_global_acceptance_distribution(
276
+ self, n_bins: int = 10, training: bool = False
277
+ ) -> tuple[Int[Array, "n_bin n_loop"], Float[Array, "n_bin n_loop"]]:
278
+ """
279
+ Get the global acceptance distribution as a histogram per epoch.
280
+
281
+ Returns:
282
+ axis (Device Array): Axis of the histogram.
283
+ hist (Device Array): Histogram of the global acceptance distribution.
284
+ """
285
+ if training is True:
286
+ n_loop = self.n_loop_training
287
+ global_accs = self.summary["training"]["global_accs"]
288
+ else:
289
+ n_loop = self.n_loop_production
290
+ global_accs = self.summary["production"]["global_accs"]
291
+
292
+ hist = [
293
+ jnp.histogram(
294
+ global_accs[
295
+ :,
296
+ i
297
+ * (self.n_global_steps // self.output_thinning - 1) : (i + 1)
298
+ * (self.n_global_steps // self.output_thinning - 1),
299
+ ].mean(axis=1),
300
+ bins=n_bins,
301
+ )
302
+ for i in range(n_loop)
303
+ ]
304
+ axis = jnp.array([hist[i][1][:-1] for i in range(n_loop)]).T
305
+ hist = jnp.array([hist[i][0] for i in range(n_loop)]).T
306
+ return axis, hist
307
+
308
+ def get_local_acceptance_distribution(
309
+ self, n_bins: int = 10, training: bool = False
310
+ ) -> tuple[Int[Array, "n_bin n_loop"], Float[Array, "n_bin n_loop"]]:
311
+ """
312
+ Get the local acceptance distribution as a histogram per epoch.
313
+
314
+ Returns:
315
+ axis (Device Array): Axis of the histogram.
316
+ hist (Device Array): Histogram of the local acceptance distribution.
317
+ """
318
+ if training is True:
319
+ n_loop = self.n_loop_training
320
+ local_accs = self.summary["training"]["local_accs"]
321
+ else:
322
+ n_loop = self.n_loop_production
323
+ local_accs = self.summary["production"]["local_accs"]
324
+
325
+ hist = [
326
+ jnp.histogram(
327
+ local_accs[
328
+ :,
329
+ i
330
+ * (self.n_local_steps // self.output_thinning - 1) : (i + 1)
331
+ * (self.n_local_steps // self.output_thinning - 1),
332
+ ].mean(axis=1),
333
+ bins=n_bins,
334
+ )
335
+ for i in range(n_loop)
336
+ ]
337
+ axis = jnp.array([hist[i][1][:-1] for i in range(n_loop)]).T
338
+ hist = jnp.array([hist[i][0] for i in range(n_loop)]).T
339
+ return axis, hist
340
+
341
+ def get_log_prob_distribution(
342
+ self, n_bins: int = 10, training: bool = False
343
+ ) -> tuple[Int[Array, "n_bin n_loop"], Float[Array, "n_bin n_loop"]]:
344
+ """
345
+ Get the log probability distribution as a histogram per epoch.
346
+
347
+ Returns:
348
+ axis (Device Array): Axis of the histogram.
349
+ hist (Device Array): Histogram of the log probability distribution.
350
+ """
351
+ if training is True:
352
+ n_loop = self.n_loop_training
353
+ log_prob = self.summary["training"]["log_prob"]
354
+ else:
355
+ n_loop = self.n_loop_production
356
+ log_prob = self.summary["production"]["log_prob"]
357
+
358
+ hist = [
359
+ jnp.histogram(
360
+ log_prob[
361
+ :,
362
+ i
363
+ * (self.n_local_steps // self.output_thinning - 1) : (i + 1)
364
+ * (self.n_local_steps // self.output_thinning - 1),
365
+ ].mean(axis=1),
366
+ bins=n_bins,
367
+ )
368
+ for i in range(n_loop)
369
+ ]
370
+ axis = jnp.array([hist[i][1][:-1] for i in range(n_loop)]).T
371
+ hist = jnp.array([hist[i][0] for i in range(n_loop)]).T
372
+ return axis, hist
373
+
374
+ def save_summary(self, path: str):
375
+ """
376
+ Save the summary to a file.
377
+
378
+ Args:
379
+ path (str): Path to save the summary.
380
+ """
381
+ with open(path, "wb") as f:
382
+ pickle.dump(self.summary, f)