2Ctypes wrapper for ParameterOptimization C++ bindings.
4This module provides low-level ctypes bindings to interface with the native
5Helios ParameterOptimization plugin via the C++ wrapper layer.
7Unlike the other wrappers, this one passes Python callables into C++: the
8objective is invoked once per candidate parameter set, potentially thousands of
9times per run. Two mechanisms make that safe and are documented in detail at
10their definitions below -- the exception stash (a Python exception cannot
11propagate through C++ frames) and the callback keepalive (a garbage-collected
12CFUNCTYPE object leaves C++ calling into freed memory).
18from typing
import Callable, Dict, List, Optional, Sequence, Tuple
20from ..plugins
import helios_lib
21from ..exceptions
import check_helios_error
26 """Opaque structure for ParameterOptimization C++ class"""
34PARAMOPT_CALLBACK_FAILED = -2
43CROSSOVER_BLX_ALPHA = 0
52 """Automatic error checking for all parameter optimization functions"""
53 check_helios_error(helios_lib.getLastErrorCode, helios_lib.getLastErrorMessage, helios_lib.clearError)
67 """One optimizable parameter, flattened for the C ABI."""
69 (
"name", ctypes.c_char_p),
70 (
"value", ctypes.c_float),
71 (
"min", ctypes.c_float),
72 (
"max", ctypes.c_float),
73 (
"type", ctypes.c_int),
74 (
"categories", ctypes.POINTER(ctypes.c_float)),
75 (
"category_count", ctypes.c_uint),
80 """Genetic algorithm settings, with the variant members flattened."""
82 (
"generations", ctypes.c_size_t),
83 (
"population_size", ctypes.c_size_t),
84 (
"crossover_rate", ctypes.c_float),
85 (
"elitism_rate", ctypes.c_float),
86 (
"random_seed", ctypes.c_uint),
87 (
"crossover_kind", ctypes.c_int),
88 (
"crossover_alpha", ctypes.c_float),
89 (
"crossover_pca_update_interval", ctypes.c_size_t),
90 (
"mutation_kind", ctypes.c_int),
91 (
"mutation_rate", ctypes.c_float),
92 (
"mutation_pca_update_interval", ctypes.c_size_t),
93 (
"mutation_sigma_pca", ctypes.c_float),
94 (
"mutation_gamma_cauchy", ctypes.c_float),
95 (
"mutation_sigma_random", ctypes.c_float),
96 (
"mutation_pca_gaussian_prob", ctypes.c_float),
97 (
"mutation_pca_cauchy_prob", ctypes.c_float),
102 """Bayesian optimization settings."""
104 (
"max_evaluations", ctypes.c_size_t),
105 (
"initial_samples", ctypes.c_size_t),
106 (
"ucb_kappa", ctypes.c_float),
107 (
"max_gp_samples", ctypes.c_size_t),
108 (
"acquisition_samples", ctypes.c_size_t),
109 (
"random_seed", ctypes.c_uint),
114 """CMA-ES settings."""
116 (
"max_evaluations", ctypes.c_size_t),
117 (
"lambda_", ctypes.c_size_t),
118 (
"sigma", ctypes.c_float),
119 (
"random_seed", ctypes.c_uint),
124 """L-BFGS settings."""
126 (
"max_iterations", ctypes.c_int),
127 (
"ftol_rel", ctypes.c_double),
128 (
"xtol_rel", ctypes.c_double),
129 (
"verify_gradients", ctypes.c_int),
130 (
"fd_step", ctypes.c_double),
135 """AdamW settings."""
137 (
"max_iterations", ctypes.c_int),
138 (
"learning_rate", ctypes.c_float),
139 (
"beta1", ctypes.c_float),
140 (
"beta2", ctypes.c_float),
141 (
"epsilon", ctypes.c_float),
142 (
"weight_decay", ctypes.c_float),
143 (
"ftol_rel", ctypes.c_double),
144 (
"xtol_rel", ctypes.c_double),
149 """BOBYQA settings."""
151 (
"max_iterations", ctypes.c_int),
152 (
"ftol_rel", ctypes.c_double),
153 (
"xtol_rel", ctypes.c_double),
154 (
"initial_step", ctypes.c_double),
159 """SLSQP settings."""
161 (
"max_iterations", ctypes.c_int),
162 (
"ftol_rel", ctypes.c_double),
163 (
"xtol_rel", ctypes.c_double),
178ObjectiveCallback = ctypes.CFUNCTYPE(
180 ctypes.POINTER(ctypes.c_float),
183 ctypes.POINTER(ctypes.c_int),
186GradientCallback = ctypes.CFUNCTYPE(
188 ctypes.POINTER(ctypes.c_float),
190 ctypes.POINTER(ctypes.c_float),
192 ctypes.POINTER(ctypes.c_int),
195ConstrainedCallback = ctypes.CFUNCTYPE(
197 ctypes.POINTER(ctypes.c_float),
199 ctypes.POINTER(ctypes.c_float),
200 ctypes.POINTER(ctypes.c_float),
201 ctypes.POINTER(ctypes.c_float),
202 ctypes.POINTER(ctypes.c_float),
205 ctypes.POINTER(ctypes.c_int),
214 helios_lib.createParameterOptimization.argtypes = []
215 helios_lib.createParameterOptimization.restype = ctypes.POINTER(UParameterOptimization)
216 helios_lib.createParameterOptimization.errcheck = _check_error
218 helios_lib.destroyParameterOptimization.argtypes = [ctypes.POINTER(UParameterOptimization)]
219 helios_lib.destroyParameterOptimization.restype =
None
222 helios_lib.parameterOptimizationAlgorithmAvailable.argtypes = [ctypes.c_char_p]
223 helios_lib.parameterOptimizationAlgorithmAvailable.restype = ctypes.c_int
226 helios_lib.setParameterOptimizationGeneticAlgorithm.argtypes = [
227 ctypes.POINTER(UParameterOptimization), ctypes.POINTER(PyHeliosGeneticAlgorithm)]
228 helios_lib.setParameterOptimizationGeneticAlgorithm.restype =
None
229 helios_lib.setParameterOptimizationGeneticAlgorithm.errcheck = _check_error
231 helios_lib.setParameterOptimizationBayesian.argtypes = [
232 ctypes.POINTER(UParameterOptimization), ctypes.POINTER(PyHeliosBayesianOptimization)]
233 helios_lib.setParameterOptimizationBayesian.restype =
None
234 helios_lib.setParameterOptimizationBayesian.errcheck = _check_error
236 helios_lib.setParameterOptimizationCMAES.argtypes = [
237 ctypes.POINTER(UParameterOptimization), ctypes.POINTER(PyHeliosCMAES)]
238 helios_lib.setParameterOptimizationCMAES.restype =
None
239 helios_lib.setParameterOptimizationCMAES.errcheck = _check_error
241 helios_lib.setParameterOptimizationAdam.argtypes = [
242 ctypes.POINTER(UParameterOptimization), ctypes.POINTER(PyHeliosAdam)]
243 helios_lib.setParameterOptimizationAdam.restype =
None
244 helios_lib.setParameterOptimizationAdam.errcheck = _check_error
246 helios_lib.setParameterOptimizationLBFGS.argtypes = [
247 ctypes.POINTER(UParameterOptimization), ctypes.POINTER(PyHeliosLBFGS)]
248 helios_lib.setParameterOptimizationLBFGS.restype =
None
249 helios_lib.setParameterOptimizationLBFGS.errcheck = _check_error
251 helios_lib.setParameterOptimizationBOBYQA.argtypes = [
252 ctypes.POINTER(UParameterOptimization), ctypes.POINTER(PyHeliosBOBYQA)]
253 helios_lib.setParameterOptimizationBOBYQA.restype =
None
254 helios_lib.setParameterOptimizationBOBYQA.errcheck = _check_error
256 helios_lib.setParameterOptimizationSLSQP.argtypes = [
257 ctypes.POINTER(UParameterOptimization), ctypes.POINTER(PyHeliosSLSQP)]
258 helios_lib.setParameterOptimizationSLSQP.restype =
None
259 helios_lib.setParameterOptimizationSLSQP.errcheck = _check_error
261 for _name, _struct
in (
262 (
"getParameterOptimizationGADefaults", PyHeliosGeneticAlgorithm),
263 (
"getParameterOptimizationGAExplore", PyHeliosGeneticAlgorithm),
264 (
"getParameterOptimizationGAExploit", PyHeliosGeneticAlgorithm),
265 (
"getParameterOptimizationBayesianDefaults", PyHeliosBayesianOptimization),
266 (
"getParameterOptimizationBayesianExplore", PyHeliosBayesianOptimization),
267 (
"getParameterOptimizationBayesianExploit", PyHeliosBayesianOptimization),
268 (
"getParameterOptimizationCMAESDefaults", PyHeliosCMAES),
269 (
"getParameterOptimizationCMAESExplore", PyHeliosCMAES),
270 (
"getParameterOptimizationCMAESExploit", PyHeliosCMAES),
271 (
"getParameterOptimizationLBFGSDefaults", PyHeliosLBFGS),
272 (
"getParameterOptimizationAdamDefaults", PyHeliosAdam),
273 (
"getParameterOptimizationBOBYQADefaults", PyHeliosBOBYQA),
274 (
"getParameterOptimizationSLSQPDefaults", PyHeliosSLSQP),
276 _fn = getattr(helios_lib, _name)
277 _fn.argtypes = [ctypes.POINTER(_struct)]
280 helios_lib.setParameterOptimizationPrintProgress.argtypes = [
281 ctypes.POINTER(UParameterOptimization), ctypes.c_int]
282 helios_lib.setParameterOptimizationPrintProgress.restype =
None
283 helios_lib.setParameterOptimizationPrintProgress.errcheck = _check_error
285 for _name
in (
"setParameterOptimizationResultFile",
286 "setParameterOptimizationProgressFile",
287 "setParameterOptimizationInputFile"):
288 _fn = getattr(helios_lib, _name)
289 _fn.argtypes = [ctypes.POINTER(UParameterOptimization), ctypes.c_char_p]
291 _fn.errcheck = _check_error
298 helios_lib.runParameterOptimization.argtypes = [
299 ctypes.POINTER(UParameterOptimization),
300 ctypes.POINTER(PyHeliosParameterSpec),
304 ctypes.POINTER(ctypes.c_float),
305 ctypes.POINTER(ctypes.c_float),
307 helios_lib.runParameterOptimization.restype = ctypes.c_int
309 helios_lib.runParameterOptimizationWithGradient.argtypes = [
310 ctypes.POINTER(UParameterOptimization),
311 ctypes.POINTER(PyHeliosParameterSpec),
316 ctypes.POINTER(ctypes.c_float),
317 ctypes.POINTER(ctypes.c_float),
319 helios_lib.runParameterOptimizationWithGradient.restype = ctypes.c_int
321 helios_lib.runParameterOptimizationWithFDGradient.argtypes = [
322 ctypes.POINTER(UParameterOptimization),
323 ctypes.POINTER(PyHeliosParameterSpec),
328 ctypes.POINTER(ctypes.c_float),
329 ctypes.POINTER(ctypes.c_float),
331 helios_lib.runParameterOptimizationWithFDGradient.restype = ctypes.c_int
333 helios_lib.runParameterOptimizationConstrained.argtypes = [
334 ctypes.POINTER(UParameterOptimization),
335 ctypes.POINTER(PyHeliosParameterSpec),
340 ctypes.POINTER(ctypes.c_float),
341 ctypes.POINTER(ctypes.c_float),
343 helios_lib.runParameterOptimizationConstrained.restype = ctypes.c_int
345 _PARAMETEROPTIMIZATION_FUNCTIONS_AVAILABLE =
True
347except AttributeError:
348 _PARAMETEROPTIMIZATION_FUNCTIONS_AVAILABLE =
False
352 """Check if ParameterOptimization functions are available in this build"""
353 return _PARAMETEROPTIMIZATION_FUNCTIONS_AVAILABLE
357 """Raise an actionable error if the plugin was not built into the library."""
358 if not _PARAMETEROPTIMIZATION_FUNCTIONS_AVAILABLE:
359 raise NotImplementedError(
360 "ParameterOptimization functions not available in current Helios library. "
361 "Rebuild PyHelios with the parameteroptimization plugin enabled:\n"
362 " build_scripts/build_helios --clean"
372 Carries a Python exception raised inside a callback back to the caller.
374 A Python exception cannot propagate through the intervening C++ frames.
375 Worse, ctypes does not even let it escape the callback: it prints the
376 traceback via PyErr_WriteUnraisable and returns 0 to C++, and 0 is a
377 perfectly plausible objective value, so the optimizer would carry on and
378 return a confidently wrong answer.
380 Instead the trampoline catches everything, stores it here, and raises the
381 error flag. The native side unwinds and returns PARAMOPT_CALLBACK_FAILED,
382 and the caller re-raises the stored exception with its original traceback.
385 __slots__ = (
"exception",)
388 self.exception: Optional[Tuple] =
None
392 names: Sequence[str],
393 state: _CallbackState) ->
"ctypes._CFuncPtr":
394 """Wrap a Python objective as a C callback."""
396 def _impl(values_ptr, n, user_data, error_flag_ptr):
399 if state.exception
is not None:
400 error_flag_ptr[0] = 1
403 params = {names[i]: values_ptr[i]
for i
in range(n)}
406 result = float(objective(params))
407 if not math.isfinite(result):
409 f
"Objective function returned {result}, which is not a finite number. "
410 f
"Non-finite objective values corrupt the optimizer's internal state "
411 f
"without raising an error, so they are rejected here."
414 except BaseException:
417 state.exception = sys.exc_info()
418 error_flag_ptr[0] = 1
425 names: Sequence[str],
426 state: _CallbackState) ->
"ctypes._CFuncPtr":
427 """Wrap a Python gradient function as a C callback."""
429 def _impl(values_ptr, n, out_gradient_ptr, user_data, error_flag_ptr):
430 if state.exception
is not None:
431 error_flag_ptr[0] = 1
434 params = {names[i]: values_ptr[i]
for i
in range(n)}
435 result = gradient(params)
437 if not isinstance(result, dict):
439 f
"Gradient function must return a dict mapping parameter name to "
440 f
"partial derivative, got {type(result).__name__}"
446 missing = set(names) - set(result)
449 f
"Gradient function omitted parameter(s) {sorted(missing)}. "
450 f
"It must return a partial derivative for every parameter: {sorted(names)}"
452 extra = set(result) - set(names)
455 f
"Gradient function returned unknown parameter(s) {sorted(extra)}. "
456 f
"Expected exactly: {sorted(names)}"
459 for i, name
in enumerate(names):
460 value = float(result[name])
461 if not math.isfinite(value):
463 f
"Gradient for parameter '{name}' is {value}, which is not a finite number."
465 out_gradient_ptr[i] = value
467 except BaseException:
468 state.exception = sys.exc_info()
469 error_flag_ptr[0] = 1
474def _write_gradient_dict(result, names: Sequence[str], out_ptr, offset: int, label: str) ->
None:
476 Validate a {name: partial derivative} mapping and write it out positionally.
478 Shared by the objective and every constraint of a constrained simulation, so
479 all of them report a missing or unknown parameter the same way. `offset` is the
480 starting index in a flat row-major buffer.
482 if not isinstance(result, dict):
484 f
"{label} must be a dict mapping parameter name to partial derivative, "
485 f
"got {type(result).__name__}"
488 missing = set(names) - set(result)
491 f
"{label} omitted parameter(s) {sorted(missing)}. "
492 f
"It must return a partial derivative for every parameter: {sorted(names)}"
494 extra = set(result) - set(names)
497 f
"{label} returned unknown parameter(s) {sorted(extra)}. "
498 f
"Expected exactly: {sorted(names)}"
501 for i, name
in enumerate(names):
502 value = float(result[name])
503 if not math.isfinite(value):
505 f
"{label} for parameter '{name}' is {value}, which is not a finite number."
507 out_ptr[offset + i] = value
511 names: Sequence[str],
512 constraint_count: int,
513 state: _CallbackState) ->
"ctypes._CFuncPtr":
514 """Wrap a Python constrained simulation as a C callback."""
516 def _impl(values_ptr, n, out_objective_ptr, out_obj_gradient_ptr,
517 out_constraints_ptr, out_con_gradients_ptr, n_constraints,
518 user_data, error_flag_ptr):
519 if state.exception
is not None:
520 error_flag_ptr[0] = 1
523 params = {names[i]: values_ptr[i]
for i
in range(n)}
524 result = simulation(params)
526 for attribute
in (
"objective",
"objective_gradient",
527 "constraints",
"constraint_gradients"):
528 if not hasattr(result, attribute):
530 f
"Constrained simulation must return a ConstrainedResult; got "
531 f
"{type(result).__name__}, which has no '{attribute}' attribute."
534 objective = float(result.objective)
535 if not math.isfinite(objective):
537 f
"Constrained simulation returned objective {objective}, which is not a "
538 f
"finite number. Non-finite values corrupt the optimizer's internal "
539 f
"state without raising an error, so they are rejected here."
541 out_objective_ptr[0] = objective
544 out_obj_gradient_ptr, 0,
"Objective gradient")
546 constraints = list(result.constraints)
547 if len(constraints) != n_constraints:
549 f
"Constrained simulation returned {len(constraints)} constraint value(s) "
550 f
"but constraint_count={n_constraints} was declared. The count is fixed "
551 f
"for the whole run and must not vary between evaluations."
553 gradients = list(result.constraint_gradients)
554 if len(gradients) != n_constraints:
556 f
"Constrained simulation returned {len(gradients)} constraint gradient(s) "
557 f
"but {n_constraints} constraint value(s). Every constraint needs exactly "
561 for i, value
in enumerate(constraints):
563 if not math.isfinite(value):
565 f
"Constraint {i} is {value}, which is not a finite number."
567 out_constraints_ptr[i] = value
570 for i, gradient
in enumerate(gradients):
572 i * len(names), f
"Gradient of constraint {i}")
574 except BaseException:
575 state.exception = sys.exc_info()
576 error_flag_ptr[0] = 1
581def _reraise(state: _CallbackState) ->
None:
582 """Re-raise the exception a callback stashed, preserving its traceback."""
583 exc_info = state.exception
584 state.exception =
None
590 "ParameterOptimization reported a callback failure but no Python exception "
591 "was recorded. This indicates an internal inconsistency in the callback bridge."
593 _, exc_value, exc_traceback = exc_info
594 raise exc_value.with_traceback(exc_traceback)
599 Build the C parameter array.
601 Returns the array along with a keepalive list holding every buffer the array
602 points into. The array stores borrowed pointers, so those buffers must
603 outlive the native call -- see the keepalive note in the run functions.
605 count = len(parameters)
606 array = (PyHeliosParameterSpec * count)()
609 for i, spec
in enumerate(parameters):
610 encoded_name = spec[
"name"].encode(
"utf-8")
611 keepalive.append(encoded_name)
613 array[i].name = encoded_name
614 array[i].value = spec[
"value"]
615 array[i].min = spec[
"min"]
616 array[i].max = spec[
"max"]
617 array[i].type = spec[
"type"]
619 categories = spec.get(
"categories")
or ()
621 category_array = (ctypes.c_float * len(categories))(*categories)
622 keepalive.append(category_array)
623 array[i].categories = category_array
624 array[i].category_count = len(categories)
626 array[i].categories =
None
627 array[i].category_count = 0
629 keepalive.append(array)
630 return array, keepalive
638 """Create a ParameterOptimization instance."""
640 return helios_lib.createParameterOptimization()
644 """Destroy a ParameterOptimization instance."""
645 if opt
and _PARAMETEROPTIMIZATION_FUNCTIONS_AVAILABLE:
646 helios_lib.destroyParameterOptimization(opt)
651 Check whether an algorithm can run in this build.
653 L-BFGS, BOBYQA and SLSQP depend on NLopt, and L-BFGS additionally on the
654 LGPL Luksan solvers that implement it.
657 algorithm_name: One of "GA", "BO", "CMAES", "LBFGS", "ADAM", "BOBYQA", "SLSQP"
659 if not _PARAMETEROPTIMIZATION_FUNCTIONS_AVAILABLE:
661 return bool(helios_lib.parameterOptimizationAlgorithmAvailable(algorithm_name.encode(
"utf-8")))
669 """Select the genetic algorithm."""
671 helios_lib.setParameterOptimizationGeneticAlgorithm(opt, ctypes.byref(settings))
675 """Select Bayesian optimization."""
677 helios_lib.setParameterOptimizationBayesian(opt, ctypes.byref(settings))
680def setCMAES(opt, settings: PyHeliosCMAES) ->
None:
683 helios_lib.setParameterOptimizationCMAES(opt, ctypes.byref(settings))
686def setAdam(opt, settings: PyHeliosAdam) ->
None:
689 helios_lib.setParameterOptimizationAdam(opt, ctypes.byref(settings))
692def setLBFGS(opt, settings: PyHeliosLBFGS) ->
None:
695 helios_lib.setParameterOptimizationLBFGS(opt, ctypes.byref(settings))
701 helios_lib.setParameterOptimizationBOBYQA(opt, ctypes.byref(settings))
704def setSLSQP(opt, settings: PyHeliosSLSQP) ->
None:
707 helios_lib.setParameterOptimizationSLSQP(opt, ctypes.byref(settings))
715 """Read a settings preset from the native library."""
717 settings = struct_type()
718 getattr(helios_lib, function_name)(ctypes.byref(settings))
723 """Get the plugin's default genetic algorithm settings."""
724 return _fetch_preset(
"getParameterOptimizationGADefaults", PyHeliosGeneticAlgorithm)
728 """Get the exploration-biased genetic algorithm preset."""
729 return _fetch_preset(
"getParameterOptimizationGAExplore", PyHeliosGeneticAlgorithm)
733 """Get the exploitation-biased genetic algorithm preset."""
734 return _fetch_preset(
"getParameterOptimizationGAExploit", PyHeliosGeneticAlgorithm)
738 """Get the plugin's default Bayesian optimization settings."""
739 return _fetch_preset(
"getParameterOptimizationBayesianDefaults", PyHeliosBayesianOptimization)
743 """Get the exploration-biased Bayesian optimization preset."""
744 return _fetch_preset(
"getParameterOptimizationBayesianExplore", PyHeliosBayesianOptimization)
748 """Get the exploitation-biased Bayesian optimization preset."""
749 return _fetch_preset(
"getParameterOptimizationBayesianExploit", PyHeliosBayesianOptimization)
753 """Get the plugin's default CMA-ES settings."""
754 return _fetch_preset(
"getParameterOptimizationCMAESDefaults", PyHeliosCMAES)
758 """Get the exploration-biased CMA-ES preset."""
763 """Get the exploitation-biased CMA-ES preset."""
764 return _fetch_preset(
"getParameterOptimizationCMAESExploit", PyHeliosCMAES)
768 """Get the plugin's default L-BFGS settings."""
769 return _fetch_preset(
"getParameterOptimizationLBFGSDefaults", PyHeliosLBFGS)
773 """Get the plugin's default Adam settings."""
774 return _fetch_preset(
"getParameterOptimizationAdamDefaults", PyHeliosAdam)
778 """Get the plugin's default BOBYQA settings."""
779 return _fetch_preset(
"getParameterOptimizationBOBYQADefaults", PyHeliosBOBYQA)
783 """Get the plugin's default SLSQP settings."""
784 return _fetch_preset(
"getParameterOptimizationSLSQPDefaults", PyHeliosSLSQP)
792 """Enable or disable the plugin's progress printout."""
794 helios_lib.setParameterOptimizationPrintProgress(opt, 1
if enable
else 0)
798 """Set the file the final result is written to (.csv or .txt)."""
800 helios_lib.setParameterOptimizationResultFile(opt, path.encode(
"utf-8")
if path
else None)
804 """Set the file per-generation progress is written to (.csv or .txt)."""
806 helios_lib.setParameterOptimizationProgressFile(opt, path.encode(
"utf-8")
if path
else None)
810 """Set a file to read the initial parameter set from."""
812 helios_lib.setParameterOptimizationInputFile(opt, path.encode(
"utf-8")
if path
else None)
819def _finish_run(rc: int, state: _CallbackState, names: Sequence[str],
820 out_values: ctypes.Array, out_fitness: ctypes.c_float) -> Tuple[Dict[str, float], float]:
821 """Translate a native return code into a result or an exception."""
822 if rc == PARAMOPT_CALLBACK_FAILED:
824 if rc != PARAMOPT_OK:
826 check_helios_error(helios_lib.getLastErrorCode, helios_lib.getLastErrorMessage,
827 helios_lib.clearError)
828 raise RuntimeError(
"ParameterOptimization run failed without reporting an error message.")
830 values = {name: out_values[i]
for i, name
in enumerate(names)}
831 return values, out_fitness.value
835 objective: Callable[[Dict[str, float]], float]) -> Tuple[Dict[str, float], float]:
837 Run a derivative-free optimization.
840 opt: ParameterOptimization instance pointer
841 parameters: Parameter specs as dicts with keys name/value/min/max/type/categories
842 objective: Callable receiving {name: value} and returning a scalar cost
845 Tuple of ({name: optimized value}, fitness)
849 raise ValueError(
"Parameter list cannot be empty")
852 names = sorted(spec[
"name"]
for spec
in parameters)
861 out_values = (ctypes.c_float * len(names))()
862 out_fitness = ctypes.c_float()
864 rc = helios_lib.runParameterOptimization(
865 opt, array, len(parameters), objective_cb,
None,
866 out_values, ctypes.byref(out_fitness))
869 del keepalive, objective_cb
871 return _finish_run(rc, state, names, out_values, out_fitness)
875 objective: Callable[[Dict[str, float]], float],
876 gradient: Callable[[Dict[str, float]], Dict[str, float]]
877 ) -> Tuple[Dict[str, float], float]:
879 Run an optimization with a user-supplied gradient.
882 opt: ParameterOptimization instance pointer
883 parameters: Parameter specs as dicts
884 objective: Callable receiving {name: value} and returning a scalar cost
885 gradient: Callable receiving {name: value} and returning {name: partial derivative}
888 Tuple of ({name: optimized value}, fitness)
892 raise ValueError(
"Parameter list cannot be empty")
894 names = sorted(spec[
"name"]
for spec
in parameters)
901 out_values = (ctypes.c_float * len(names))()
902 out_fitness = ctypes.c_float()
904 rc = helios_lib.runParameterOptimizationWithGradient(
905 opt, array, len(parameters), objective_cb, gradient_cb,
None,
906 out_values, ctypes.byref(out_fitness))
908 del keepalive, objective_cb, gradient_cb
910 return _finish_run(rc, state, names, out_values, out_fitness)
914 objective: Callable[[Dict[str, float]], float],
915 fd_step: float = 0.0) -> Tuple[Dict[str, float], float]:
917 Run an optimization with gradients estimated by finite differences.
920 opt: ParameterOptimization instance pointer
921 parameters: Parameter specs as dicts
922 objective: Callable receiving {name: value} and returning a scalar cost
923 fd_step: Relative perturbation factor; values <= 0 select the plugin default
926 Tuple of ({name: optimized value}, fitness)
930 raise ValueError(
"Parameter list cannot be empty")
932 names = sorted(spec[
"name"]
for spec
in parameters)
938 out_values = (ctypes.c_float * len(names))()
939 out_fitness = ctypes.c_float()
941 rc = helios_lib.runParameterOptimizationWithFDGradient(
942 opt, array, len(parameters), objective_cb, fd_step,
None,
943 out_values, ctypes.byref(out_fitness))
945 del keepalive, objective_cb
947 return _finish_run(rc, state, names, out_values, out_fitness)
951 simulation: Callable[[Dict[str, float]], object],
952 constraint_count: int) -> Tuple[Dict[str, float], float]:
954 Run a constrained optimization: minimize f(x) subject to c_i(x) <= 0.
956 Requires SLSQP, enforced by the plugin.
959 opt: ParameterOptimization instance pointer
960 parameters: Parameter specs as dicts
961 simulation: Callable receiving {name: value} and returning a ConstrainedResult
962 constraint_count: Number of constraints; fixed for the whole run
965 Tuple of ({name: optimized value}, fitness)
969 raise ValueError(
"Parameter list cannot be empty")
970 if constraint_count < 1:
972 f
"constraint_count must be at least 1, got {constraint_count}. "
973 f
"Use runOptimization() or runOptimizationWithGradient() when there are "
976 names = sorted(spec[
"name"]
for spec
in parameters)
982 out_values = (ctypes.c_float * len(names))()
983 out_fitness = ctypes.c_float()
985 rc = helios_lib.runParameterOptimizationConstrained(
986 opt, array, len(parameters), simulation_cb, constraint_count,
None,
987 out_values, ctypes.byref(out_fitness))
989 del keepalive, simulation_cb
991 return _finish_run(rc, state, names, out_values, out_fitness)
995if not _PARAMETEROPTIMIZATION_FUNCTIONS_AVAILABLE:
998 "Mock mode: ParameterOptimization not available. "
999 "This would create a parameter optimization instance with native library."
1004 "Mock mode: ParameterOptimization methods not available. "
1005 "This would run an optimization with native library."
1009 createParameterOptimization = mock_createParameterOptimization
1010 runOptimization = mock_runParameterOptimization
1011 runOptimizationWithGradient = mock_runParameterOptimization
1012 runOptimizationWithFDGradient = mock_runParameterOptimization
1013 runOptimizationConstrained = mock_runParameterOptimization
Bayesian optimization settings.
Genetic algorithm settings, with the variant members flattened.
One optimizable parameter, flattened for the C ABI.
Opaque structure for ParameterOptimization C++ class.
Carries a Python exception raised inside a callback back to the caller.
_check_error(result, func, args)
Automatic error checking for all parameter optimization functions.
PyHeliosCMAES getCMAESDefaults()
Get the plugin's default CMA-ES settings.
PyHeliosGeneticAlgorithm getGeneticAlgorithmExploit()
Get the exploitation-biased genetic algorithm preset.
PyHeliosGeneticAlgorithm getGeneticAlgorithmExplore()
Get the exploration-biased genetic algorithm preset.
None setLBFGS(opt, PyHeliosLBFGS settings)
Select L-BFGS.
runOptimizationWithFDGradient
mock_createParameterOptimization(*args, **kwargs)
None setCMAES(opt, PyHeliosCMAES settings)
Select CMA-ES.
PyHeliosGeneticAlgorithm getGeneticAlgorithmDefaults()
Get the plugin's default genetic algorithm settings.
None _write_gradient_dict(result, Sequence[str] names, out_ptr, int offset, str label)
Validate a {name: partial derivative} mapping and write it out positionally.
None setResultFile(opt, Optional[str] path)
Set the file the final result is written to (.csv or .txt).
None setBayesianOptimization(opt, PyHeliosBayesianOptimization settings)
Select Bayesian optimization.
runOptimizationConstrained
None setProgressFile(opt, Optional[str] path)
Set the file per-generation progress is written to (.csv or .txt).
PyHeliosCMAES getCMAESExplore()
Get the exploration-biased CMA-ES preset.
bool isParameterOptimizationAvailable()
Check if ParameterOptimization functions are available in this build.
"ctypes._CFuncPtr" _make_gradient_trampoline(Callable[[Dict[str, float]], Dict[str, float]] gradient, Sequence[str] names, _CallbackState state)
Wrap a Python gradient function as a C callback.
createParameterOptimization
Create a ParameterOptimization instance.
None _require_available()
Raise an actionable error if the plugin was not built into the library.
PyHeliosBayesianOptimization getBayesianExplore()
Get the exploration-biased Bayesian optimization preset.
Tuple[ctypes.Array, list] _build_parameter_array(List[dict] parameters)
Build the C parameter array.
None setSLSQP(opt, PyHeliosSLSQP settings)
Select SLSQP.
PyHeliosBayesianOptimization getBayesianDefaults()
Get the plugin's default Bayesian optimization settings.
Tuple[Dict[str, float], float] _finish_run(int rc, _CallbackState state, Sequence[str] names, ctypes.Array out_values, ctypes.c_float out_fitness)
Translate a native return code into a result or an exception.
None destroyParameterOptimization(opt)
Destroy a ParameterOptimization instance.
_fetch_preset(str function_name, struct_type)
Read a settings preset from the native library.
None setBOBYQA(opt, PyHeliosBOBYQA settings)
Select BOBYQA.
PyHeliosCMAES getCMAESExploit()
Get the exploitation-biased CMA-ES preset.
PyHeliosAdam getAdamDefaults()
Get the plugin's default Adam settings.
bool isAlgorithmAvailable(str algorithm_name)
Check whether an algorithm can run in this build.
PyHeliosSLSQP getSLSQPDefaults()
Get the plugin's default SLSQP settings.
mock_runParameterOptimization(*args, **kwargs)
None setGeneticAlgorithm(opt, PyHeliosGeneticAlgorithm settings)
Select the genetic algorithm.
PyHeliosBOBYQA getBOBYQADefaults()
Get the plugin's default BOBYQA settings.
None setAdam(opt, PyHeliosAdam settings)
Select AdamW.
None _reraise(_CallbackState state)
Re-raise the exception a callback stashed, preserving its traceback.
runOptimizationWithGradient
PyHeliosBayesianOptimization getBayesianExploit()
Get the exploitation-biased Bayesian optimization preset.
None setPrintProgress(opt, bool enable)
Enable or disable the plugin's progress printout.
PyHeliosLBFGS getLBFGSDefaults()
Get the plugin's default L-BFGS settings.
None setInputFile(opt, Optional[str] path)
Set a file to read the initial parameter set from.
"ctypes._CFuncPtr" _make_objective_trampoline(Callable[[Dict[str, float]], float] objective, Sequence[str] names, _CallbackState state)
Wrap a Python objective as a C callback.
"ctypes._CFuncPtr" _make_constrained_trampoline(Callable[[Dict[str, float]], object] simulation, Sequence[str] names, int constraint_count, _CallbackState state)
Wrap a Python constrained simulation as a C callback.