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.

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.

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