Coverage for dynasor/post_processing/average_runs.py: 100%
51 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-10 08:27 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-10 08:27 +0000
1from copy import deepcopy
2from typing import Optional
3import numpy as np
4from dynasor.sample import Sample
7def get_sample_averaged_over_independent_runs(
8 samples: list[Sample],
9 live_dangerously: Optional[bool] = False,
10) -> Sample:
11 """
12 Compute an averaged sample from multiple samples obtained from identical independent runs.
14 Note all the metadata and dimensions in all samples must be the same.
15 Otherwise a `ValueError` is raised (unless `live_dangerously` is set to `True`).
17 Parameters
18 ----------
19 samples
20 List of all sample objects to be averaged over.
21 live_dangerously
22 Setting to `True` allows for averaging over samples
23 which metadata information is not identical.
24 """
26 if len(samples) < 2:
27 raise ValueError('Averaging requires at least two samples.')
29 # get metadata and dimensions from first sample
30 sample_ref = samples[0]
31 data_dict = dict()
32 simulation_data = deepcopy(sample_ref.simulation_data)
34 # test that all samples have identical dimensions and correlation functions
35 for m, sample in enumerate(samples):
36 if sorted(sample.dimensions) != sorted(sample_ref.dimensions):
37 raise ValueError(f'Sample dimensions do not match for sample #{m}.')
38 for dim in sample_ref.dimensions:
39 if not np.allclose(sample[dim], sample_ref[dim]):
40 raise ValueError(f'Sample dimensions do not match for sample #{m}.')
41 if sample.available_correlation_functions != sample_ref.available_correlation_functions:
42 raise ValueError(f'Sample correlation functions do not match for sample #{m}.')
44 for dim in sample_ref.dimensions:
45 data_dict[dim] = sample_ref[dim]
47 # test that all samples have identical metadata
48 if not live_dangerously:
49 for m, sample in enumerate(samples):
50 for key, val in simulation_data.items():
51 if key not in sample.simulation_data:
52 raise ValueError(
53 f'Sample #{m} is missing "{key}" in the simulation_data field.')
54 match = True
55 if isinstance(val, dict):
56 for k, v in val.items():
57 match &= sample.simulation_data[key].get(k, None) == val[k]
58 elif isinstance(val, np.ndarray):
59 match &= np.allclose(sample.simulation_data[key], val)
60 elif isinstance(val, float):
61 match &= np.isclose(sample.simulation_data[key], val)
62 else:
63 match &= sample.simulation_data[key] == val
64 if not match:
65 raise ValueError(f'Field "{key}" of sample #{m} does not match.')
67 # average all correlation functions
68 for key in sample_ref.available_correlation_functions:
69 data = []
70 for sample in samples:
71 data.append(sample[key])
72 data_average = np.nanmean(data, axis=0)
73 data_dict[key] = data_average
75 # keep history of original samples
76 previous_history = []
77 for m, s in enumerate(samples):
78 for h in s.history:
79 rec = h.copy()
80 rec['func'] += f'_sample{m}'
81 previous_history.append(rec)
83 # compose new sample object
84 new_sample = sample_ref.__class__(
85 data_dict,
86 simulation_data=simulation_data,
87 history=previous_history)
88 new_sample._append_history(
89 'get_sample_averaged_over_independent_runs',
90 dict(
91 live_dangerously=live_dangerously,
92 n_samples=len(samples),
93 ))
95 return new_sample