ropt 0.1.0__py3-none-any.whl

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 (99) hide show
  1. ropt/__init__.py +1 -0
  2. ropt/apps/__init__.py +7 -0
  3. ropt/apps/_script_optimizer.py +463 -0
  4. ropt/config/__init__.py +1 -0
  5. ropt/config/enopt/__init__.py +27 -0
  6. ropt/config/enopt/_enopt_base_model.py +15 -0
  7. ropt/config/enopt/_enopt_config.py +208 -0
  8. ropt/config/enopt/_function_transform_config.py +43 -0
  9. ropt/config/enopt/_gradient_config.py +119 -0
  10. ropt/config/enopt/_linear_constraints_config.py +83 -0
  11. ropt/config/enopt/_nonlinear_constraints_config.py +112 -0
  12. ropt/config/enopt/_objective_functions_config.py +100 -0
  13. ropt/config/enopt/_optimizer_config.py +84 -0
  14. ropt/config/enopt/_realization_filter_config.py +46 -0
  15. ropt/config/enopt/_realizations_config.py +85 -0
  16. ropt/config/enopt/_sampler_config.py +52 -0
  17. ropt/config/enopt/_utils.py +132 -0
  18. ropt/config/enopt/_variables_config.py +184 -0
  19. ropt/config/enopt/constants.py +36 -0
  20. ropt/config/plan/__init__.py +26 -0
  21. ropt/config/plan/_config.py +156 -0
  22. ropt/config/plan/_plan_config.py +21 -0
  23. ropt/config/plan/_step_config.py +27 -0
  24. ropt/config/utils.py +185 -0
  25. ropt/enums.py +175 -0
  26. ropt/evaluator/__init__.py +50 -0
  27. ropt/evaluator/_concurrent.py +189 -0
  28. ropt/evaluator/_ensemble_evaluator.py +653 -0
  29. ropt/evaluator/_evaluator.py +143 -0
  30. ropt/evaluator/_evaluator_results.py +244 -0
  31. ropt/evaluator/_function.py +69 -0
  32. ropt/evaluator/_gradient.py +229 -0
  33. ropt/evaluator/_utils.py +25 -0
  34. ropt/evaluator/parsl.py +161 -0
  35. ropt/events.py +39 -0
  36. ropt/exceptions.py +25 -0
  37. ropt/optimization/__init__.py +18 -0
  38. ropt/optimization/_bases.py +122 -0
  39. ropt/optimization/_ensemble_optimizer.py +118 -0
  40. ropt/optimization/_events.py +27 -0
  41. ropt/optimization/_optimizer.py +246 -0
  42. ropt/optimization/_plan.py +218 -0
  43. ropt/plugins/__init__.py +39 -0
  44. ropt/plugins/_manager.py +156 -0
  45. ropt/plugins/function_transform/__init__.py +5 -0
  46. ropt/plugins/function_transform/default.py +152 -0
  47. ropt/plugins/function_transform/protocol.py +72 -0
  48. ropt/plugins/optimization_steps/__init__.py +7 -0
  49. ropt/plugins/optimization_steps/_utils.py +91 -0
  50. ropt/plugins/optimization_steps/default.py +58 -0
  51. ropt/plugins/optimization_steps/enopt_config.py +37 -0
  52. ropt/plugins/optimization_steps/evaluator.py +112 -0
  53. ropt/plugins/optimization_steps/label.py +29 -0
  54. ropt/plugins/optimization_steps/optimizer.py +139 -0
  55. ropt/plugins/optimization_steps/reset_tracker.py +34 -0
  56. ropt/plugins/optimization_steps/restart.py +57 -0
  57. ropt/plugins/optimization_steps/tracker.py +88 -0
  58. ropt/plugins/optimization_steps/update_config.py +51 -0
  59. ropt/plugins/optimizer/__init__.py +21 -0
  60. ropt/plugins/optimizer/protocol.py +105 -0
  61. ropt/plugins/optimizer/scipy.py +557 -0
  62. ropt/plugins/optimizer/utils.py +247 -0
  63. ropt/plugins/realization_filter/__init__.py +11 -0
  64. ropt/plugins/realization_filter/default.py +384 -0
  65. ropt/plugins/realization_filter/protocol.py +58 -0
  66. ropt/plugins/sampler/__init__.py +14 -0
  67. ropt/plugins/sampler/protocol.py +87 -0
  68. ropt/plugins/sampler/scipy.py +199 -0
  69. ropt/py.typed +0 -0
  70. ropt/report/__init__.py +16 -0
  71. ropt/report/_data_frame.py +212 -0
  72. ropt/report/_table.py +127 -0
  73. ropt/report/_utils.py +116 -0
  74. ropt/results/__init__.py +85 -0
  75. ropt/results/_bound_constraints.py +174 -0
  76. ropt/results/_function_evaluations.py +161 -0
  77. ropt/results/_function_results.py +103 -0
  78. ropt/results/_functions.py +120 -0
  79. ropt/results/_gradient_evaluations.py +95 -0
  80. ropt/results/_gradient_results.py +90 -0
  81. ropt/results/_gradients.py +61 -0
  82. ropt/results/_linear_constraints.py +87 -0
  83. ropt/results/_maximize.py +64 -0
  84. ropt/results/_nonlinear_constraints.py +129 -0
  85. ropt/results/_pandas.py +104 -0
  86. ropt/results/_realizations.py +67 -0
  87. ropt/results/_result_field.py +40 -0
  88. ropt/results/_results.py +227 -0
  89. ropt/results/_utils.py +104 -0
  90. ropt/results/_xarray.py +131 -0
  91. ropt/utils/__init__.py +9 -0
  92. ropt/utils/_misc.py +27 -0
  93. ropt/utils/scaling.py +147 -0
  94. ropt/version.py +16 -0
  95. ropt-0.1.0.dist-info/LICENSE +674 -0
  96. ropt-0.1.0.dist-info/METADATA +82 -0
  97. ropt-0.1.0.dist-info/RECORD +99 -0
  98. ropt-0.1.0.dist-info/WHEEL +5 -0
  99. ropt-0.1.0.dist-info/top_level.txt +1 -0
ropt/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """The main `ropt` module, a library for ensemble based optimization."""
ropt/apps/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Simple applications to run optimization workflows."""
2
+
3
+ from ._script_optimizer import ScriptOptimizer
4
+
5
+ __all__ = [
6
+ "ScriptOptimizer",
7
+ ]
@@ -0,0 +1,463 @@
1
+ """A class for running optimizations with script-based jobs."""
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ from collections import abc, defaultdict
7
+ from dataclasses import dataclass, field
8
+ from itertools import groupby
9
+ from pathlib import Path
10
+ from string import Template
11
+ from traceback import format_exception
12
+ from typing import (
13
+ Any,
14
+ Callable,
15
+ DefaultDict,
16
+ Dict,
17
+ List,
18
+ Optional,
19
+ Sequence,
20
+ TextIO,
21
+ Tuple,
22
+ Union,
23
+ no_type_check,
24
+ )
25
+
26
+ import numpy as np
27
+ from numpy.typing import NDArray
28
+ from parsl.app.app import bash_app
29
+ from parsl.providers.base import ExecutionProvider
30
+ from tabulate import tabulate
31
+
32
+ from ropt.config.enopt import EnOptConfig
33
+ from ropt.enums import EventType, OptimizerExitCode
34
+ from ropt.evaluator import EvaluatorContext
35
+ from ropt.evaluator.parsl import ParslEvaluator, State, Task
36
+ from ropt.events import OptimizationEvent
37
+ from ropt.exceptions import ConfigError
38
+ from ropt.optimization import EnsembleOptimizer
39
+ from ropt.results import FunctionResults
40
+
41
+
42
+ def _make_dict(
43
+ variables: NDArray[np.float64], names: Sequence[Tuple[str, ...]]
44
+ ) -> DefaultDict[str, Any]:
45
+ def recursive_dict() -> DefaultDict[str, Any]:
46
+ return defaultdict(recursive_dict)
47
+
48
+ var_dict = recursive_dict()
49
+ for idx, var_name in enumerate(names):
50
+ tmp = var_dict
51
+ for name in var_name[:-1]:
52
+ tmp = tmp[str(name)]
53
+ tmp[str(var_name[-1])] = variables[idx]
54
+
55
+ return var_dict
56
+
57
+
58
+ def _write_dict(var_dict: Dict[str, Any], path: Path, name: str, depth: int) -> None:
59
+ if depth == 0 or not isinstance(var_dict, abc.Mapping):
60
+ filename = (path / name if name else path / "variables").with_suffix(".json")
61
+ with filename.open("w", encoding="utf-8") as file_obj:
62
+ json.dump(var_dict, file_obj, indent=2)
63
+ else:
64
+ for key, value in var_dict.items():
65
+ _write_dict(value, path, f"{name}_{key}" if name else key, depth - 1)
66
+
67
+
68
+ def variables_to_json(
69
+ variables: NDArray[np.float64], names: Tuple[Any, ...], path: Path, depth: int = 0
70
+ ) -> None:
71
+ """Export a vector of variables with given names to json.
72
+
73
+ The `names` parameter must provide the name of each variable stored in
74
+ variables. Each name should be a tuple, which will be used to generate a
75
+ nested dictionary entry with the variable value. The generated dict is
76
+ stored in one or more json files depending on the value of `depth`. If
77
+ `depth == 0`, the dictionary is saved as a single file with the name
78
+ `variables.json`. If `depth > 0` files are generated by generating a json
79
+ file for each entry given by the first `depth`th components of the name. The
80
+ name of each file is constructed by concatenating the first `depth`th name
81
+ components.
82
+
83
+ The `level` parameter must be at least zero. The `path` parameters must
84
+ denote an existing directory.
85
+
86
+ Args:
87
+ variables: The variables to export
88
+ names: The names of the variables
89
+ path: The directory to store the file
90
+ depth: The depth at which to generate files
91
+ """
92
+ msg = ""
93
+ if depth < 0:
94
+ msg = "The depth parameter must be at least zero"
95
+ if not path.is_dir():
96
+ msg = "The path parameters should point to a valid directory"
97
+ if variables.ndim != 1:
98
+ msg = "The variables parameter must be a 1D vector"
99
+ if len(names) != variables.size:
100
+ msg = "The length of the names parameter must match the variables size"
101
+ if msg:
102
+ raise ValueError(msg)
103
+ return _write_dict(_make_dict(variables, names), path, "", depth)
104
+
105
+
106
+ @dataclass
107
+ class ScriptTask(Task):
108
+ """Task class for use by the ScriptBasedOptimizer class."""
109
+
110
+ name: str = ""
111
+ job_labels: Tuple[str, ...] = field(default_factory=tuple)
112
+ objective_paths: Tuple[Path, ...] = field(default_factory=tuple)
113
+ constraint_paths: Tuple[Path, ...] = field(default_factory=tuple)
114
+ logged: bool = False
115
+ realization: int = 0
116
+ _objectives: Optional[NDArray[np.float64]] = None
117
+ _constraints: Optional[NDArray[np.float64]] = None
118
+
119
+ def _read_file(self, path: Path) -> float:
120
+ try:
121
+ with path.open("r", encoding="utf-8") as file_obj:
122
+ return float(file_obj.read())
123
+ except BaseException as exc: # noqa: BLE001
124
+ self.exception = exc
125
+ return np.nan
126
+
127
+ def _read_results(self) -> None:
128
+ if self.objective_paths:
129
+ self._objectives = np.zeros(len(self.objective_paths), dtype=np.float64)
130
+ for idx, path in enumerate(self.objective_paths):
131
+ self._objectives[idx] = self._read_file(path)
132
+ if self.constraint_paths:
133
+ self._constraints = np.zeros(len(self.constraint_paths), dtype=np.float64)
134
+ for idx, path in enumerate(self.constraint_paths):
135
+ self._constraints[idx] = self._read_file(path)
136
+
137
+ def get_objectives(self) -> Optional[NDArray[np.float64]]:
138
+ """Get the objective values.
139
+
140
+ Returns:
141
+ The objective values.
142
+ """
143
+ self._read_results()
144
+ return self._objectives
145
+
146
+ def get_constraints(self) -> Optional[NDArray[np.float64]]:
147
+ """Get the constraint values.
148
+
149
+ Returns:
150
+ The constraint values.
151
+ """
152
+ self._read_results()
153
+ return self._constraints
154
+
155
+
156
+ @no_type_check
157
+ @bash_app()
158
+ def run_script(
159
+ _: Any, # noqa: ANN401
160
+ script: str,
161
+ stdout: TextIO, # noqa: ARG001
162
+ stderr: TextIO, # noqa: ARG001
163
+ ) -> str:
164
+ return script
165
+
166
+
167
+ def get_function_files(config: EnOptConfig) -> Tuple[Tuple[str, ...], Tuple[str, ...]]:
168
+ """Default function to retrieve names of the files with evaluation results.
169
+
170
+ Args:
171
+ config: Optimizer configuration file.
172
+
173
+ Returns:
174
+ Two tuples with filenames of objective and constraint functions.
175
+ """
176
+ assert config.objective_functions.names is not None
177
+ objective_names = tuple(str(name) for name in config.objective_functions.names)
178
+ if config.nonlinear_constraints is not None:
179
+ assert config.nonlinear_constraints.names is not None
180
+ constraint_names = tuple(
181
+ str(name) for name in config.nonlinear_constraints.names
182
+ )
183
+ else:
184
+ constraint_names = ()
185
+ return objective_names, constraint_names
186
+
187
+
188
+ class ScriptOptimizer:
189
+ """Optimizer class for running script bases optimization workflows."""
190
+
191
+ def __init__( # noqa: PLR0913
192
+ self,
193
+ plan: Sequence[Dict[str, Any]],
194
+ tasks: Dict[str, str],
195
+ work_dir: Union[Path, str],
196
+ *,
197
+ job_dir: Optional[Union[Path, str]] = None,
198
+ job_labels: Tuple[str, ...] = ("B{batch:04d}", "J{job:04d}"),
199
+ variable_export_depth: int = 0,
200
+ get_function_files: Callable[
201
+ [EnOptConfig], Tuple[Tuple[str, ...], Tuple[str, ...]]
202
+ ] = get_function_files,
203
+ callbacks: Optional[Dict[EventType, Callable[..., None]]] = None,
204
+ seed: Optional[int] = None,
205
+ provider: Optional[ExecutionProvider] = None,
206
+ max_threads: int = 4,
207
+ ) -> None:
208
+ """Initialize the optimizer.
209
+
210
+ The directory where the jobs run is constructed from by nesting
211
+ directories according the `job_labels` tuple. These directories are
212
+ located in the directory where the optimizer runs, which can be set
213
+ using the `work_dir` parameter of the `run()` method.
214
+
215
+ Args:
216
+ plan: The optimization plan to run
217
+ tasks: A dictionary mapping task names to strings
218
+ containing bash code
219
+ work_dir: Working directory
220
+ job_dir: The directory to store files generated during
221
+ optimization
222
+ job_labels: Label formats to use in generating filenames
223
+ reports
224
+ variable_export_depth: The depth parameter for exporting variables
225
+ get_function_files: Callable to retrieve function file names
226
+ callbacks: Dictionary of callbacks for the optimizer
227
+ seed: Seed for the random number generator
228
+ provider: The provider that executes the jobs
229
+ max_threads: Maximum number of threads for local runs
230
+ """
231
+ self._plan = plan
232
+ self._tasks = tasks
233
+ self._work_dir = Path(work_dir).resolve()
234
+ self._job_labels = job_labels
235
+ self._variable_export_depth = variable_export_depth
236
+ self._get_function_files = get_function_files
237
+ self._callbacks = callbacks
238
+ self._seed = seed
239
+ self._provider = provider
240
+ self._max_threads = max_threads
241
+ self._status: Dict[int, Any] = {}
242
+ self._optimal_result: Optional[FunctionResults] = None
243
+
244
+ self._job_dir = self._work_dir if job_dir is None else Path(job_dir)
245
+ if not self._job_dir.is_absolute():
246
+ self._job_dir = self._work_dir / self._job_dir
247
+
248
+ def _set_logger(self) -> None:
249
+ self._logger = logging.getLogger("ScriptBasedOptimizer")
250
+ self._logger.setLevel(level=logging.INFO)
251
+ handler = logging.FileHandler("optimizer.log")
252
+ formatter = logging.Formatter("%(levelname)s: %(message)s")
253
+ handler.setFormatter(formatter)
254
+ self._logger.addHandler(handler)
255
+
256
+ def _workflow(
257
+ self,
258
+ batch_id: int,
259
+ job_idx: int,
260
+ variables: NDArray[np.float64],
261
+ context: EvaluatorContext,
262
+ ) -> List[ScriptTask]:
263
+ assert context.config.realizations.names is not None
264
+ realization = context.config.realizations.names[context.realizations[job_idx]]
265
+ if not isinstance(realization, int):
266
+ msg = f"Realization name must be an integer: {realization}"
267
+ raise ConfigError(msg)
268
+
269
+ job_labels = tuple(
270
+ label.format(batch=batch_id, realization=realization, job=job_idx)
271
+ for label in self._job_labels
272
+ )
273
+
274
+ path = self._job_dir / Path(*job_labels)
275
+ Path.mkdir(path, parents=True, exist_ok=True)
276
+
277
+ assert context.config.variables.names is not None
278
+ variables_to_json(
279
+ variables[job_idx],
280
+ context.config.variables.names,
281
+ path,
282
+ depth=self._variable_export_depth,
283
+ )
284
+
285
+ tasks: List[ScriptTask] = []
286
+ for task_name, script in self._tasks.items():
287
+ substituted_script = Template(script).safe_substitute(
288
+ work_dir=self._work_dir, realization=realization
289
+ )
290
+ tasks.append(
291
+ ScriptTask(
292
+ name=task_name,
293
+ future=run_script(
294
+ tasks[-1].future if tasks else None,
295
+ f"cd {path}\n{substituted_script}",
296
+ stdout=((path / task_name).with_suffix(".stdout"), "a"),
297
+ stderr=((path / task_name).with_suffix(".stderr"), "a"),
298
+ ),
299
+ job_labels=job_labels,
300
+ realization=realization,
301
+ ),
302
+ )
303
+
304
+ objective_names, constraint_names = self._get_function_files(context.config)
305
+ tasks[-1].objective_paths = tuple(path / name for name in objective_names)
306
+ tasks[-1].constraint_paths = tuple(path / name for name in constraint_names)
307
+
308
+ return tasks
309
+
310
+ def _log_task(self, task: ScriptTask) -> None:
311
+ if not task.logged:
312
+ job_label = ", ".join(task.job_labels)
313
+
314
+ if task.state == State.FAILED:
315
+ assert task.exception is not None
316
+ msg = f"{job_label}, {task.name}: FAILED\n"
317
+ msg += "".join(
318
+ format_exception(
319
+ type(task.exception),
320
+ task.exception,
321
+ task.exception.__traceback__,
322
+ ),
323
+ )
324
+ self._logger.error(msg)
325
+ task.logged = True
326
+
327
+ if task.state == State.SUCCESS:
328
+ msg = f"{job_label}, {task.name}: FINISHED"
329
+ self._logger.info(msg)
330
+ task.logged = True
331
+
332
+ def _write_state_report(
333
+ self, batch_id: int, jobs: Dict[int, List[ScriptTask]]
334
+ ) -> None:
335
+ states: DefaultDict[str, Dict[int, State]] = defaultdict(dict)
336
+ for job_idx, tasks in jobs.items():
337
+ for task in tasks:
338
+ states[task.name][job_idx] = task.state
339
+ table: List[Dict[str, Any]] = []
340
+ for name, task_states in states.items():
341
+ job_name = name
342
+ for state in State:
343
+ job_indices = [
344
+ idx for idx, item in task_states.items() if item == state
345
+ ]
346
+ if job_indices:
347
+ table.append(
348
+ {
349
+ "Task": job_name,
350
+ "State": state.value,
351
+ "Jobs": _format_list(job_indices),
352
+ },
353
+ )
354
+ job_name = ""
355
+ with (self._work_dir / "status.txt").open("w", encoding="utf-8") as file_obj:
356
+ file_obj.write(f"Batch: {batch_id}\n\n")
357
+ table_str = tabulate(table, headers="keys", tablefmt="simple")
358
+ file_obj.write(f"{table_str}\n")
359
+
360
+ def _update_current_state(
361
+ self, batch_id: int, jobs: Dict[int, List[ScriptTask]]
362
+ ) -> None:
363
+ # Update the current batch
364
+ states: DefaultDict[int, Dict[str, Any]] = defaultdict(dict)
365
+ for job_idx, tasks in jobs.items():
366
+ for task in tasks:
367
+ states[job_idx][task.name] = {
368
+ "state": task.state.value,
369
+ "realization": task.realization,
370
+ }
371
+ self._log_task(task)
372
+ self._status[batch_id] = states
373
+
374
+ def _store_states(self) -> None:
375
+ states_json = []
376
+ for batch_id in sorted(self._status.keys()):
377
+ states = self._status[batch_id]
378
+ states_json.append(
379
+ {
380
+ "batch_id": batch_id,
381
+ "jobs": [
382
+ {
383
+ "job": job_idx,
384
+ "tasks": [
385
+ {
386
+ "name": task_name,
387
+ "state": task_state["state"],
388
+ "realization": task_state["realization"],
389
+ }
390
+ for task_name, task_state in states[job_idx].items()
391
+ ],
392
+ }
393
+ for job_idx in sorted(self._status[batch_id])
394
+ ],
395
+ },
396
+ )
397
+ with (self._work_dir / "states.json").open("w", encoding="utf-8") as file_obj:
398
+ json.dump(states_json, file_obj, sort_keys=True, indent=4)
399
+
400
+ def _monitor(self, batch_id: int, jobs: Dict[int, List[ScriptTask]]) -> None:
401
+ self._write_state_report(batch_id, jobs)
402
+ self._update_current_state(batch_id, jobs)
403
+ self._store_states()
404
+
405
+ def _log_exit_code(self, event: OptimizationEvent) -> None:
406
+ exit_code = event.exit_code
407
+ assert event.config is not None
408
+ if exit_code == OptimizerExitCode.TOO_FEW_REALIZATIONS:
409
+ self._logger.warning(
410
+ "Too few successful realizations: optimization stopped.",
411
+ )
412
+ elif exit_code == OptimizerExitCode.MAX_FUNCTIONS_REACHED:
413
+ self._logger.warning(
414
+ "Maximum number of functions reached: optimization stopped.",
415
+ )
416
+ elif exit_code == OptimizerExitCode.USER_ABORT:
417
+ self._logger.warning("Optimization aborted by the user.")
418
+ elif exit_code == OptimizerExitCode.OPTIMIZER_STEP_FINISHED:
419
+ self._logger.info("Optimization finished normally.")
420
+
421
+ def _handle_finished_optimizer(self, event: OptimizationEvent) -> None:
422
+ self._log_exit_code(event)
423
+
424
+ def run(self) -> Dict[str, Optional[FunctionResults]]:
425
+ """Run the optimization."""
426
+ cwd = Path.cwd()
427
+ Path.mkdir(self._work_dir, parents=True, exist_ok=True)
428
+ os.chdir(self._work_dir)
429
+
430
+ try:
431
+ self._set_logger()
432
+ evaluator = ParslEvaluator(
433
+ self._workflow,
434
+ monitor=self._monitor,
435
+ provider=self._provider,
436
+ max_threads=self._max_threads,
437
+ )
438
+ optimizer = EnsembleOptimizer(evaluator)
439
+ optimizer.add_observer(
440
+ EventType.FINISHED_OPTIMIZER_STEP,
441
+ self._handle_finished_optimizer,
442
+ )
443
+ if self._callbacks is not None:
444
+ for event, callback in self._callbacks.items():
445
+ optimizer.add_observer(event, callback)
446
+ optimizer.start_optimization(self._plan, seed=self._seed)
447
+ finally:
448
+ os.chdir(cwd)
449
+
450
+ return optimizer.results
451
+
452
+
453
+ def _format_list(values: List[int]) -> str:
454
+ grouped = (
455
+ tuple(y for _, y in x)
456
+ for _, x in groupby(enumerate(sorted(values)), lambda x: x[0] - x[1])
457
+ )
458
+ return ", ".join(
459
+ "-".join([str(sub_group[0]), str(sub_group[-1])])
460
+ if len(sub_group) > 1
461
+ else str(sub_group[0])
462
+ for sub_group in grouped
463
+ )
@@ -0,0 +1 @@
1
+ """The `ropt.config` module contains configuration related code."""
@@ -0,0 +1,27 @@
1
+ """The `ropt.config.enopt` module contains optimization configuration classes."""
2
+
3
+ from ._enopt_config import EnOptConfig
4
+ from ._function_transform_config import FunctionTransformConfig
5
+ from ._gradient_config import GradientConfig
6
+ from ._linear_constraints_config import LinearConstraintsConfig
7
+ from ._nonlinear_constraints_config import NonlinearConstraintsConfig
8
+ from ._objective_functions_config import ObjectiveFunctionsConfig
9
+ from ._optimizer_config import OptimizerConfig
10
+ from ._realization_filter_config import RealizationFilterConfig
11
+ from ._realizations_config import RealizationsConfig
12
+ from ._sampler_config import SamplerConfig
13
+ from ._variables_config import VariablesConfig
14
+
15
+ __all__ = [
16
+ "EnOptConfig",
17
+ "FunctionTransformConfig",
18
+ "GradientConfig",
19
+ "LinearConstraintsConfig",
20
+ "NonlinearConstraintsConfig",
21
+ "ObjectiveFunctionsConfig",
22
+ "OptimizerConfig",
23
+ "RealizationFilterConfig",
24
+ "RealizationsConfig",
25
+ "SamplerConfig",
26
+ "VariablesConfig",
27
+ ]
@@ -0,0 +1,15 @@
1
+ """Base model class for `EnOptConfig` fields."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+
8
+ class EnOptBaseModel(BaseModel):
9
+ model_config = ConfigDict(
10
+ arbitrary_types_allowed=True,
11
+ extra="forbid",
12
+ str_min_length=1,
13
+ str_strip_whitespace=True,
14
+ validate_default=True,
15
+ )