persistent_sampling¶
Persistent generator providing points using sampling
- persistent_sampling.persistent_uniform(_, persis_info, gen_specs, libE_info)¶
This generation function always enters into persistent mode and returns
gen_specs["initial_batch_size"]uniformly sampled points the first time it is called. Afterwards, it returns the number of points given. This can be used in either a batch or asynchronous mode by adjusting the allocation function.
- persistent_sampling.persistent_uniform_final_update(_, persis_info, gen_specs, libE_info)¶
Assuming the value
"f"returned from sim_f is stochastic, this generation is updating an estimated mean"f_est"of the sim_f output at each of the corners of the domain.
- persistent_sampling.persistent_request_shutdown(_, persis_info, gen_specs, libE_info)¶
This generation function is similar in structure to persistent_uniform, but uses a count to test exiting on a threshold value. This principle can be used with a supporting allocation function (e.g. start_only_persistent) to shutdown an ensemble when a condition is met.
- persistent_sampling.uniform_nonblocking(_, persis_info, gen_specs, libE_info)¶
This generation function is designed to test non-blocking receives.
See also
- persistent_sampling.batched_history_matching(_, persis_info, gen_specs, libE_info)¶
Given - sim_f with an input of x with len(x)=n - b, the batch size of points to generate - q<b, the number of best samples to use in the following iteration
Pseudocode: Let (mu, Sigma) denote a mean and covariance matrix initialized to the origin and the identity, respectively.
While true (batch synchronous for now):
Draw b samples x_1, … , x_b from MVN( mu, Sigma) Evaluate f(x_1), … , f(x_b) and determine the set of q x_i whose f(x_i) values are smallest (breaking ties lexicographically) Update (mu, Sigma) based on the sample mean and sample covariance of these q x values.
See also
- persistent_sampling.persistent_uniform_with_cancellations(_, persis_info, gen_specs, libE_info)¶
persistent_sampling.py
1"""Persistent generator providing points using sampling"""
2
3import numpy as np
4
5from libensemble.message_numbers import EVAL_GEN_TAG, FINISHED_PERSISTENT_GEN_TAG, PERSIS_STOP, STOP_TAG
6from libensemble.tools import get_rng
7from libensemble.tools.persistent_support import PersistentSupport
8
9__all__ = [
10 "persistent_uniform",
11 "persistent_uniform_final_update",
12 "persistent_request_shutdown",
13 "uniform_nonblocking",
14 "batched_history_matching",
15 "persistent_uniform_with_cancellations",
16]
17
18
19def _get_user_params(user_specs, gen_specs):
20 """Extract user params"""
21 b = gen_specs.get("initial_batch_size") or user_specs.get("initial_batch_size") or gen_specs.get("init_sample_size")
22 ub = user_specs["ub"]
23 lb = user_specs["lb"]
24 n = len(lb) # dimension
25 assert isinstance(b, int), "Batch size must be an integer"
26 assert isinstance(n, int), "Dimension must be an integer"
27 assert isinstance(lb, np.ndarray), "lb must be a numpy array"
28 assert isinstance(ub, np.ndarray), "ub must be a numpy array"
29 return b, n, lb, ub
30
31
32def persistent_uniform(_, persis_info, gen_specs, libE_info):
33 """
34 This generation function always enters into persistent mode and returns
35 ``gen_specs["initial_batch_size"]`` uniformly sampled points the first time it
36 is called. Afterwards, it returns the number of points given. This can be
37 used in either a batch or asynchronous mode by adjusting the allocation
38 function.
39
40 .. seealso::
41 `test_persistent_uniform_sampling.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_sampling.py>`_
42 `test_persistent_uniform_sampling_async.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_sampling_async.py>`_
43 """ # noqa
44 rng = get_rng(gen_specs, libE_info)
45
46 b, n, lb, ub = _get_user_params(gen_specs.get("user", {}), gen_specs)
47 ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
48
49 # Send batches until manager sends stop tag
50 tag = None
51 while tag not in [STOP_TAG, PERSIS_STOP]:
52 H_o = np.zeros(b, dtype=gen_specs["out"])
53 H_o["x"] = rng.uniform(lb, ub, (b, n))
54 if "obj_component" in H_o.dtype.fields:
55 H_o["obj_component"] = rng.integers(low=0, high=gen_specs["user"]["num_components"], size=b)
56 tag, Work, calc_in = ps.send_recv(H_o)
57 if hasattr(calc_in, "__len__"):
58 b = len(calc_in)
59
60 return None, persis_info, FINISHED_PERSISTENT_GEN_TAG
61
62
63def persistent_uniform_final_update(_, persis_info, gen_specs, libE_info):
64 """
65 Assuming the value ``"f"`` returned from sim_f is stochastic, this
66 generation is updating an estimated mean ``"f_est"`` of the sim_f output at
67 each of the corners of the domain.
68
69 .. seealso::
70 `test_persistent_uniform_sampling_running_mean.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_sampling_running_mean.py>`_
71 """ # noqa
72
73 b, n, lb, ub = _get_user_params(gen_specs.get("user", {}), gen_specs)
74 ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
75
76 def generate_corners(x, y):
77 n = len(x)
78 corner_indices = np.arange(2**n)
79 corners = []
80 for index in corner_indices:
81 corner = [x[i] if index & (1 << i) else y[i] for i in range(n)]
82 corners.append(corner)
83 return corners
84
85 def sample_corners_with_probability(corners, p, b):
86 selected_corners = np.random.choice(len(corners), size=b, p=p)
87 sampled_corners = [corners[i] for i in selected_corners]
88 return sampled_corners, selected_corners
89
90 corners = generate_corners(lb, ub)
91
92 # Start with equal probabilities
93 p = np.ones(2**n) / 2**n
94
95 running_total = np.nan * np.ones(2**n)
96 number_of_samples = np.zeros(2**n)
97 sent = np.array([], dtype=int)
98
99 # Send batches of `b` points until manager sends stop tag
100 tag = None
101 next_id = 0
102 while tag not in [STOP_TAG, PERSIS_STOP]:
103 H_o = np.zeros(b, dtype=gen_specs["out"])
104 H_o["sim_id"] = range(next_id, next_id + b)
105 next_id += b
106
107 sampled_corners, corner_ids = sample_corners_with_probability(corners, p, b)
108
109 H_o["corner_id"] = corner_ids
110 H_o["x"] = sampled_corners
111 sent = np.append(sent, corner_ids)
112
113 tag, Work, calc_in = ps.send_recv(H_o)
114 if hasattr(calc_in, "__len__"):
115 b = len(calc_in)
116 for row in calc_in:
117 number_of_samples[row["corner_id"]] += 1
118 if np.isnan(running_total[row["corner_id"]]):
119 running_total[row["corner_id"]] = row["f"]
120 else:
121 running_total[row["corner_id"]] += row["f"]
122
123 # Having received a PERSIS_STOP, update f_est field for all points and return
124 # For manager to honor final H_o return, must have set libE_specs["use_persis_return_gen"] = True
125 f_est = running_total / number_of_samples
126 H_o = np.zeros(len(sent), dtype=[("sim_id", int), ("corner_id", int), ("f_est", float)])
127 for count, i in enumerate(sent):
128 H_o["sim_id"][count] = count
129 H_o["corner_id"][count] = i
130 H_o["f_est"][count] = f_est[i]
131
132 return H_o, persis_info, FINISHED_PERSISTENT_GEN_TAG
133
134
135def persistent_request_shutdown(_, persis_info, gen_specs, libE_info):
136 """
137 This generation function is similar in structure to persistent_uniform,
138 but uses a count to test exiting on a threshold value. This principle can
139 be used with a supporting allocation function (e.g. start_only_persistent)
140 to shutdown an ensemble when a condition is met.
141
142 .. seealso::
143 `test_persistent_uniform_gen_decides_stop.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_gen_decides_stop.py>`_
144 """ # noqa
145 rng = get_rng(gen_specs, libE_info)
146 b, n, lb, ub = _get_user_params(gen_specs.get("user", {}), gen_specs)
147 shutdown_limit = gen_specs["user"]["shutdown_limit"]
148 f_count = 0
149 ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
150
151 # Send batches until manager sends stop tag
152 tag = None
153 while tag not in [STOP_TAG, PERSIS_STOP]:
154 H_o = np.zeros(b, dtype=gen_specs["out"])
155 H_o["x"] = rng.uniform(lb, ub, (b, n))
156 tag, Work, calc_in = ps.send_recv(H_o)
157 if hasattr(calc_in, "__len__"):
158 b = len(calc_in)
159 f_count += b
160 if f_count >= shutdown_limit:
161 print("Reached threshold.", f_count, flush=True)
162 break # End the persistent gen
163
164 return None, persis_info, FINISHED_PERSISTENT_GEN_TAG
165
166
167def uniform_nonblocking(_, persis_info, gen_specs, libE_info):
168 """
169 This generation function is designed to test non-blocking receives.
170
171 .. seealso::
172 `test_persistent_uniform_sampling.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_sampling.py>`_
173 """ # noqa
174 rng = get_rng(gen_specs, libE_info)
175 b, n, lb, ub = _get_user_params(gen_specs.get("user", {}), gen_specs)
176 ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
177
178 # Send batches until manager sends stop tag
179 tag = None
180 while tag not in [STOP_TAG, PERSIS_STOP]:
181 H_o = np.zeros(b, dtype=gen_specs["out"])
182 H_o["x"] = rng.uniform(lb, ub, (b, n))
183 ps.send(H_o)
184
185 received = False
186 spin_count = 0
187 while not received:
188 tag, Work, calc_in = ps.recv(blocking=False)
189 if tag is not None:
190 received = True
191 else:
192 spin_count += 1
193
194 persis_info["spin_count"] = spin_count
195
196 if hasattr(calc_in, "__len__"):
197 b = len(calc_in)
198
199 return None, persis_info, FINISHED_PERSISTENT_GEN_TAG
200
201
202def batched_history_matching(_, persis_info, gen_specs, libE_info):
203 """
204 Given
205 - sim_f with an input of x with len(x)=n
206 - b, the batch size of points to generate
207 - q<b, the number of best samples to use in the following iteration
208
209 Pseudocode:
210 Let (mu, Sigma) denote a mean and covariance matrix initialized to the
211 origin and the identity, respectively.
212
213 While true (batch synchronous for now):
214
215 Draw b samples x_1, ... , x_b from MVN( mu, Sigma)
216 Evaluate f(x_1), ... , f(x_b) and determine the set of q x_i whose f(x_i) values are smallest (breaking ties lexicographically)
217 Update (mu, Sigma) based on the sample mean and sample covariance of these q x values.
218
219 .. seealso::
220 `test_persistent_uniform_sampling.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_sampling.py>`_
221 """ # noqa
222 rng = get_rng(gen_specs, libE_info)
223 lb = gen_specs["user"]["lb"]
224
225 n = len(lb)
226 b = (
227 gen_specs.get("initial_batch_size")
228 or gen_specs["user"].get("initial_batch_size")
229 or gen_specs.get("init_sample_size")
230 )
231 q = gen_specs["user"]["num_best_vals"]
232 ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
233
234 mu = np.zeros(n)
235 Sigma = np.eye(n)
236 tag = None
237
238 while tag not in [STOP_TAG, PERSIS_STOP]:
239 H_o = np.zeros(b, dtype=gen_specs["out"])
240 H_o["x"] = rng.multivariate_normal(mu, Sigma, b)
241
242 # Send data and get next assignment
243 tag, Work, calc_in = ps.send_recv(H_o)
244 if calc_in is not None:
245 all_inds = np.argsort(calc_in["f"])
246 best_inds = all_inds[:q]
247 mu = np.mean(H_o["x"][best_inds], axis=0)
248 Sigma = np.cov(H_o["x"][best_inds].T)
249
250 return None, persis_info, FINISHED_PERSISTENT_GEN_TAG
251
252
253def persistent_uniform_with_cancellations(_, persis_info, gen_specs, libE_info):
254 rng = get_rng(gen_specs, libE_info)
255 ub = gen_specs["user"]["ub"]
256 lb = gen_specs["user"]["lb"]
257 n = len(lb)
258 b = (
259 gen_specs.get("initial_batch_size")
260 or gen_specs["user"].get("initial_batch_size")
261 or gen_specs.get("init_sample_size")
262 )
263
264 # Start cancelling points from half initial batch onward
265 cancel_from = b // 2 # Should get at least this many points back
266
267 ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
268
269 # Send batches until manager sends stop tag
270 tag = None
271 while tag not in [STOP_TAG, PERSIS_STOP]:
272 H_o = np.zeros(b, dtype=gen_specs["out"])
273 H_o["x"] = rng.uniform(lb, ub, (b, n))
274 tag, Work, calc_in = ps.send_recv(H_o)
275
276 if hasattr(calc_in, "__len__"):
277 b = len(calc_in)
278
279 # Cancel as many points as got back
280 cancel_ids = list(range(cancel_from, cancel_from + b))
281 cancel_from += b
282 ps.request_cancel_sim_ids(cancel_ids)
283
284 return None, persis_info, FINISHED_PERSISTENT_GEN_TAG