diff --git a/.github/workflows/python_tests.yml b/.github/workflows/python_tests.yml index 6e48461..979ccce 100644 --- a/.github/workflows/python_tests.yml +++ b/.github/workflows/python_tests.yml @@ -27,10 +27,11 @@ jobs: pip install ".[test]" - name: Run the tests (Linux/macOS) if: runner.os != 'Windows' + shell: bash run: | echo -e '## Pytest Results\n\n' >> "$GITHUB_STEP_SUMMARY" echo -e '```' >> "$GITHUB_STEP_SUMMARY" - pytest -v --cov=beamfit/ tests 2>&1 | tee -a "$GITHUB_STEP_SUMMARY" + pytest -v --cov=paretobench tests 2>&1 | tee -a "$GITHUB_STEP_SUMMARY" echo -e '```' >> "$GITHUB_STEP_SUMMARY" - name: Run the tests (Windows) if: runner.os == 'Windows' diff --git a/src/paretobench/containers.py b/src/paretobench/containers.py index 94a5241..bdbe082 100644 --- a/src/paretobench/containers.py +++ b/src/paretobench/containers.py @@ -1,7 +1,7 @@ from datetime import datetime, timezone from functools import reduce from pydantic import BaseModel, Field, field_validator, ConfigDict, model_validator -from typing import List, Dict, Union, Optional, Literal, Tuple, TYPE_CHECKING +from typing import Literal, TYPE_CHECKING import h5py import numpy as np import random @@ -15,6 +15,34 @@ from .problem import Problem +def _validate_1d_float_array(value, name: str, expected_len: int, expected_len_desc: str): + """ + Checks that a field is a 1D float64 numpy array of the expected length. + + Parameters + ---------- + value : Any + The value of the field being validated. + name : str + Name of the field, used in the error messages. + expected_len : int + The length the array must have. + expected_len_desc : str + Human readable description of where the expected length comes from, used in the error message. + """ + if not isinstance(value, np.ndarray): + raise ValueError(f"{name} must be a numpy array, got: {type(value)}") + if len(value.shape) != 1: + raise ValueError(f"{name} must be 1D, shape was: {value.shape}") + if len(value) != expected_len: + raise ValueError( + f"Length of {name} must match {expected_len_desc}. Got {len(value)} elements but " + f"{expected_len_desc} is {expected_len}" + ) + if value.dtype != np.float64: + raise ValueError(f"{name} dtype must be {np.float64}. Got {value.dtype}") + + class Population(BaseModel): """ Stores the individuals in a population for one reporting interval in a genetic algorithm. Conventional names are used for @@ -28,6 +56,9 @@ class Population(BaseModel): to an objectve and '+' means maximize with '-' meaning minimize. The constraints are configured by the string of directions `constraint_directions` and the numpy array of targets `constraint_targets`. The string should contain either the '<' or '>' character for the constraint at that index to be satisfied when it is less than or greater than the target respectively. + + The rectangular bounds the decision variables were drawn from are recorded in `var_lower_bounds` and `var_upper_bounds`. + When they are not specified, the variables are treated as unbounded and the arrays are filled with -inf and +inf. """ # The decision vars, objectives, and constraints @@ -37,10 +68,11 @@ class Population(BaseModel): # Total number of function evaluations performed during optimization after this population was completed fevals: int + # Optional lists of names for decision variables, objectives, and constraints - names_x: Optional[List[str]] = None - names_f: Optional[List[str]] = None - names_g: Optional[List[str]] = None + names_x: list[str] | None = None + names_f: list[str] | None = None + names_g: list[str] | None = None # Configuration of objectives/constraints (minimization or maximization problem, direction of and target of constraint) obj_directions: str # '+' means maximize, '-' means minimize @@ -49,6 +81,10 @@ class Population(BaseModel): ) constraint_targets: np.ndarray + # Rectangular bounds of the decision variables (-inf / +inf when unbounded) + var_lower_bounds: np.ndarray + var_upper_bounds: np.ndarray + # Pydantic config model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True) @@ -58,7 +94,8 @@ def set_default_vals(cls, values): """ Handles automatic setting of `x`, `f`, `g`, `fevals` when some are not specified. The arrays are set to an empty array with a zero length non-batch dimension. The number of function evaluations (`fevals`) is set to the number of individuals - in the population (assuming here that each was evaluated to get to this point). + in the population (assuming here that each was evaluated to get to this point). Unspecified decision variable bounds + are set to -inf / +inf. """ # Determine the batch size from the first non-None array batch_size = next( @@ -84,6 +121,12 @@ def set_default_vals(cls, values): if values.get("constraint_targets") is None: values["constraint_targets"] = np.zeros(values["g"].shape[1], dtype=float) + # Treat the decision variables as unbounded if no bounds were given + if values.get("var_lower_bounds") is None: + values["var_lower_bounds"] = np.full(values["x"].shape[1], -np.inf) + if values.get("var_upper_bounds") is None: + values["var_upper_bounds"] = np.full(values["x"].shape[1], np.inf) + # Set fevals to number of individuals if not included if values.get("fevals") is None: values["fevals"] = batch_size @@ -140,19 +183,29 @@ def validate_constraint_directions(self): @model_validator(mode="after") def validate_constraint_targets(self): - # Check the targets - attr = "constraint_targets" - if not isinstance(getattr(self, attr), np.ndarray): - raise ValueError(f"{attr} must be a numpy array, got: {type(getattr(self, attr))}") - if len(getattr(self, attr).shape) != 1: - raise ValueError(f"{attr} must be 1D, shape was: {getattr(self, attr).shape}") - if len(getattr(self, attr)) != self.g.shape[1]: + _validate_1d_float_array( + self.constraint_targets, "constraint_targets", self.g.shape[1], "number of constraints in g" + ) + return self + + @model_validator(mode="after") + def validate_var_bounds(self): + """ + Checks that the decision variable bounds are correctly sized and that each lower bound is at or below its + corresponding upper bound. + """ + _validate_1d_float_array( + self.var_lower_bounds, "var_lower_bounds", self.x.shape[1], "number of decision variables in x" + ) + _validate_1d_float_array( + self.var_upper_bounds, "var_upper_bounds", self.x.shape[1], "number of decision variables in x" + ) + bad_idx = np.nonzero(self.var_lower_bounds > self.var_upper_bounds)[0] + if bad_idx.size: raise ValueError( - f"Length of {attr} must match number of constraints in g. Got {len(getattr(self, attr))} " - f"elements and {self.g.shape[1]} constraints from g" + f"var_lower_bounds must be less than or equal to var_upper_bounds. Got lower > upper at indices " + f"{bad_idx.tolist()}" ) - if getattr(self, attr).dtype != np.float64: - raise ValueError(f"{attr} dtype must be {np.float64}. Got {getattr(self, attr).dtype}") return self @field_validator("x", "f", "g") @@ -189,6 +242,8 @@ def __eq__(self, other): and self.obj_directions == other.obj_directions and self.constraint_directions == other.constraint_directions and np.array_equal(self.constraint_targets, other.constraint_targets) + and np.array_equal(self.var_lower_bounds, other.var_lower_bounds) + and np.array_equal(self.var_upper_bounds, other.var_upper_bounds) ) @property @@ -228,6 +283,10 @@ def __add__(self, other: "Population") -> "Population": raise ValueError("constraint_directions are inconsistent between populations") if not np.array_equal(self.constraint_targets, other.constraint_targets): raise ValueError("constraint_targets are inconsistent between populations") + if not np.array_equal(self.var_lower_bounds, other.var_lower_bounds): + raise ValueError("var_lower_bounds are inconsistent between populations") + if not np.array_equal(self.var_upper_bounds, other.var_upper_bounds): + raise ValueError("var_upper_bounds are inconsistent between populations") # Concatenate the arrays along the batch dimension (axis=0) new_x = np.concatenate((self.x, other.x), axis=0) @@ -255,9 +314,11 @@ def __add__(self, other: "Population") -> "Population": obj_directions=self.obj_directions, constraint_directions=self.constraint_directions, constraint_targets=self.constraint_targets, + var_lower_bounds=self.var_lower_bounds, + var_upper_bounds=self.var_upper_bounds, ) - def __getitem__(self, idx: Union[slice, np.ndarray, List[int]]) -> "Population": + def __getitem__(self, idx: slice | np.ndarray | list[int]) -> "Population": """ Indexing operator to select along the batch dimension in the arrays. @@ -282,6 +343,8 @@ def __getitem__(self, idx: Union[slice, np.ndarray, List[int]]) -> "Population": obj_directions=self.obj_directions, constraint_directions=self.constraint_directions, constraint_targets=self.constraint_targets, + var_lower_bounds=self.var_lower_bounds, + var_upper_bounds=self.var_upper_bounds, ) def get_nondominated_indices(self): @@ -308,6 +371,7 @@ def from_random( fevals: int = 0, generate_names: bool = False, generate_obj_constraint_settings: bool = False, + generate_bounds: bool = False, ) -> "Population": """ Generate a randomized instance of the Population class. @@ -328,6 +392,8 @@ def from_random( Whether to include names for the decision variables, objectives, and constraints, by default False. generate_obj_constraint_settings : bool, optional Randomize the objective and constraint settings, default to minimization problem and g >= 0 constraint + generate_bounds : bool, optional + Randomize the decision variable bounds, by default the variables are left unbounded Returns ------- @@ -371,6 +437,14 @@ def from_random( constraint_directions = None constraint_targets = None + # Create randomized bounds which contain the decision variables + if generate_bounds: + var_lower_bounds = -np.random.rand(n_decision_vars) + var_upper_bounds = 1.0 + np.random.rand(n_decision_vars) + else: + var_lower_bounds = None + var_upper_bounds = None + return cls( x=x, f=f, @@ -382,6 +456,8 @@ def from_random( obj_directions=obj_directions, constraint_directions=constraint_directions, constraint_targets=constraint_targets, + var_lower_bounds=var_lower_bounds, + var_upper_bounds=var_upper_bounds, ) def __len__(self): @@ -448,19 +524,19 @@ def plot_obj_scatter( domination_filt: Literal["all", "dominated", "non-dominated"] = "all", feasibility_filt: Literal["all", "feasible", "infeasible"] = "all", show_points: bool = True, - problem: Optional[Union[str, "Problem"]] = None, + problem: "str | Problem | None" = None, n_pf: int = 1000, - pf_objectives: Optional[np.ndarray] = None, + pf_objectives: np.ndarray | None = None, show_attainment: bool = False, show_dominated_area: bool = False, - dominated_area_zorder: Optional[int] = -2, - ref_point: Optional[Tuple[float, float]] = None, + dominated_area_zorder: int | None = -2, + ref_point: tuple[float, float] | None = None, ref_point_padding: float = 0.05, - label: Optional[str] = None, - legend_loc: Optional[str] = None, + label: str | None = None, + legend_loc: str | None = None, show_names: bool = True, - color: Optional[str] = None, - scale: Optional[np.ndarray] = None, + color: str | None = None, + scale: np.ndarray | None = None, flip_objs: bool = False, ): """ @@ -491,7 +567,7 @@ def plot_obj_scatter( Plots the dominated region towards the larger values of each decision var dominated_area_zorder : int, optional What "zorder" to draw dominated region at. Mostly used internally to correctly show dominated area in history plots. - ref_point : Union[str, Tuple[float, float]], optional + ref_point : str | tuple[float, float], optional Where to stop plotting the dominated region / attainment surface. Must be a point to the upper right (increasing value of objectives in 3D) of all plotted points. By default, will set to right of max of each objective plus padding. @@ -543,18 +619,19 @@ def plot_obj_scatter( def plot_dvar_pairs( self, - dvars: Optional[Union[int, slice, List[int], Tuple[int, int]]] = None, + dvars: int | slice | list[int] | tuple[int, int] | None = None, fig=None, axes=None, domination_filt: Literal["all", "dominated", "non-dominated"] = "all", feasibility_filt: Literal["all", "feasible", "infeasible"] = "all", - hist_bins: Optional[int] = None, + hist_bins: int | None = None, show_names: bool = True, - problem: Optional[Union[str, "Problem"]] = None, - lower_bounds: Optional[np.ndarray] = None, - upper_bounds: Optional[np.ndarray] = None, - color: Optional[str] = None, - scale: Optional[np.ndarray] = None, + problem: "str | Problem | None" = None, + lower_bounds: np.ndarray | None = None, + upper_bounds: np.ndarray | None = None, + color: str | None = None, + scale: np.ndarray | None = None, + plot_bounds: bool = True, ): """ Creates a pairs plot (scatter matrix) showing correlations between decision variables @@ -562,7 +639,7 @@ def plot_dvar_pairs( Parameters ---------- - dvars : int, slice, List[int], or Tuple[int, int], optional + dvars : int, slice, list[int], or tuple[int, int], optional Specifies which decision variables to plot. See `selection_to_indices` for more details. fig : matplotlib.figure.Figure, optional Figure to plot on. If None and axes is None, creates a new figure. @@ -580,14 +657,16 @@ def plot_dvar_pairs( problem : str/Problem, optional The problem for plotting decision variable bounds lower_bounds : array-like, optional - Lower bounds for each decision variable + Lower bounds for each decision variable. Defaults to the bounds carried by the population. upper_bounds : array-like, optional - Upper bounds for each decision variable + Upper bounds for each decision variable. Defaults to the bounds carried by the population. color : str, optional What color should we use for the points. Defaults to selecting from matplotlib color cycler scale : array-like, optional Scale factors for each variable. Must have the same length as the number of decision vars. If None, no scaling is applied. + plot_bounds : bool, optional + Whether to plot the decision variable bounds, by default True. Infinite bounds are not plotted. Returns ------- @@ -611,6 +690,7 @@ def plot_dvar_pairs( upper_bounds=upper_bounds, color=color, scale=scale, + plot_bounds=plot_bounds, ) @@ -624,9 +704,9 @@ class History(BaseModel): - Objective/constraint settings and names, if used, must be consistent across populations """ - reports: List[Population] + reports: list[Population] problem: str - metadata: Dict[str, Union[str, int, float, bool]] = Field(default_factory=dict) + metadata: dict[str, str | int | float | bool] = Field(default_factory=dict) @model_validator(mode="after") def validate_consistent_populations(self): @@ -668,6 +748,14 @@ def validate_consistent_populations(self): if constraint_targets and len(set(constraint_targets)) != 1: raise ValueError(f"Inconsistent constraint_targets in reports: {constraint_targets}") + # Check the decision variable bounds + var_lower_bounds = [tuple(x.var_lower_bounds) for x in self.reports] + var_upper_bounds = [tuple(x.var_upper_bounds) for x in self.reports] + if var_lower_bounds and len(set(var_lower_bounds)) != 1: + raise ValueError(f"Inconsistent var_lower_bounds in reports: {var_lower_bounds}") + if var_upper_bounds and len(set(var_upper_bounds)) != 1: + raise ValueError(f"Inconsistent var_upper_bounds in reports: {var_upper_bounds}") + return self def __eq__(self, other): @@ -685,6 +773,7 @@ def from_random( pop_size: int, generate_names: bool = False, generate_obj_constraint_settings: bool = False, + generate_bounds: bool = False, ) -> "History": """ Generate a randomized instance of the History class, including random problem name and metadata. @@ -705,6 +794,8 @@ def from_random( Whether to include names for the decision variables, objectives, and constraints, by default False. generate_obj_constraint_settings : bool, optional Randomize the objective and constraint settings, default to minimization problem and g >= 0 constraint + generate_bounds : bool, optional + Randomize the decision variable bounds, by default the variables are left unbounded Returns ------- @@ -748,6 +839,14 @@ def from_random( report.constraint_directions = constraint_directions report.constraint_targets = constraint_targets + # Create randomized decision variable bounds (must be consistent between objects) + if generate_bounds: + var_lower_bounds = -np.random.rand(n_decision_vars) + var_upper_bounds = 1.0 + np.random.rand(n_decision_vars) + for report in reports: + report.var_lower_bounds = var_lower_bounds + report.var_upper_bounds = var_upper_bounds + return cls(reports=reports, problem=problem, metadata=metadata) def _to_h5py_group(self, g: h5py.Group): @@ -790,6 +889,8 @@ def _to_h5py_group(self, g: h5py.Group): # Save the configuration data if self.reports: + g["x"].attrs["lower_bounds"] = self.reports[0].var_lower_bounds + g["x"].attrs["upper_bounds"] = self.reports[0].var_upper_bounds g["f"].attrs["directions"] = self.reports[0].obj_directions g["g"].attrs["directions"] = self.reports[0].constraint_directions g["g"].attrs["targets"] = self.reports[0].constraint_targets @@ -819,6 +920,10 @@ def _from_h5py_group(cls, grp: h5py.Group, file_version: str): constraint_directions = grp["g"].attrs.get("directions", None) constraint_targets = grp["g"].attrs.get("targets", None) + # Files written before version 1.2.0 have no decision variable bounds and are treated as unbounded + var_lower_bounds = grp["x"].attrs.get("lower_bounds", None) + var_upper_bounds = grp["x"].attrs.get("upper_bounds", None) + # Before file version 1.1.0 which introduced explicit constraint directions, # the default constraint type was g(x) >= 0.0 if file_version == "1.0.0": @@ -845,6 +950,8 @@ def _from_h5py_group(cls, grp: h5py.Group, file_version: str): obj_directions=obj_directions, constraint_directions=constraint_directions, constraint_targets=constraint_targets, + var_lower_bounds=var_lower_bounds, + var_upper_bounds=var_upper_bounds, ) ) start_idx += pop_size @@ -883,27 +990,27 @@ def pf_reduce(a, b): def plot_obj_scatter( self, - reports: Optional[Union[int, slice, List[int], Tuple[int, int]]] = None, + reports: int | slice | list[int] | tuple[int, int] | None = None, fig=None, ax=None, domination_filt: Literal["all", "dominated", "non-dominated"] = "all", feasibility_filt: Literal["all", "feasible", "infeasible"] = "all", show_points: bool = True, n_pf: int = 1000, - pf_objectives: Optional[np.ndarray] = None, + pf_objectives: np.ndarray | None = None, show_attainment: bool = False, show_dominated_area: bool = False, - ref_point: Optional[Tuple[float, float]] = None, + ref_point: tuple[float, float] | None = None, ref_point_padding: float = 0.05, - legend_loc: Optional[str] = None, - scale: Optional[np.ndarray] = None, + legend_loc: str | None = None, + scale: np.ndarray | None = None, flip_objs: bool = False, show_names: bool = True, show_pf: bool = False, colormap: str = "viridis", - cmap_label: Optional[str] = None, + cmap_label: str | None = None, generation_mode: Literal["cmap", "cumulative"] = "cmap", - single_color: Optional[str] = None, + single_color: str | None = None, label_mode: Literal["index", "fevals"] = "index", ): """ @@ -912,7 +1019,7 @@ def plot_obj_scatter( Parameters ---------- - reports : int, slice, List[int], or Tuple[int, int], optional + reports : int, slice, list[int], or tuple[int, int], optional Specifies which generations to plot. See `selection_to_indices` for more details. fig : matplotlib figure, optional Figure to plot on, by default None @@ -933,7 +1040,7 @@ def plot_obj_scatter( Whether to plot the attainment surface, by default False show_dominated_area : bool, optional Plots the dominated region towards the larger values of each decision var - ref_point : Union[str, Tuple[float, float]], optional + ref_point : str | tuple[float, float], optional Where to stop plotting the dominated region / attainment surface. Must be a point to the upper right (increasing value of objectives in 3D) of all plotted points. By default, will set to right of max of each objective plus padding. @@ -952,13 +1059,13 @@ def plot_obj_scatter( Whether to plot the Pareto front, by default True colormap : str, optional Name of the colormap to use for generation colors, by default 'viridis' - cmap_label: Optional[str] = "Generation" + cmap_label: str | None = "Generation" Label for colorbar (only used when generation_mode is 'cmap') generation_mode: Literal['cmap', 'cumulative'] = 'cmap' How to handle multiple generations: 'cmap': Plot each generation separately with colors from colormap 'cumulative': Merge all selected generations into single population - single_color: Optional[str] = None + single_color: str | None = None Color to use when generation_mode is 'cumulative'. If None, uses default color from matplotlib. label_mode: Literal['index', 'fevals'] = 'index' Whether to use report index or function evaluations (fevals) for labels @@ -998,21 +1105,21 @@ def plot_obj_scatter( def plot_dvar_pairs( self, - reports: Optional[Union[int, slice, List[int], Tuple[int, int]]] = None, - dvars: Optional[Union[int, slice, List[int], Tuple[int, int]]] = None, + reports: int | slice | list[int] | tuple[int, int] | None = None, + dvars: int | slice | list[int] | tuple[int, int] | None = None, fig=None, axes=None, domination_filt: Literal["all", "dominated", "non-dominated"] = "all", feasibility_filt: Literal["all", "feasible", "infeasible"] = "all", - hist_bins: Optional[int] = None, + hist_bins: int | None = None, show_names: bool = True, - lower_bounds: Optional[np.ndarray] = None, - upper_bounds: Optional[np.ndarray] = None, - scale: Optional[np.ndarray] = None, + lower_bounds: np.ndarray | None = None, + upper_bounds: np.ndarray | None = None, + scale: np.ndarray | None = None, colormap: str = "viridis", - cmap_label: Optional[str] = None, + cmap_label: str | None = None, generation_mode: Literal["cmap", "cumulative"] = "cmap", - single_color: Optional[str] = None, + single_color: str | None = None, plot_bounds: bool = False, label_mode: Literal["index", "fevals"] = "index", ): @@ -1022,9 +1129,9 @@ def plot_dvar_pairs( Parameters ---------- - reports : int, slice, List[int], or Tuple[int, int], optional + reports : int, slice, list[int], or tuple[int, int], optional Specifies which generations to plot. See `selection_to_indices` for more details. - dvars : int, slice, List[int], or Tuple[int, int], optional + dvars : int, slice, list[int], or tuple[int, int], optional Which decision vars to plot. See `population_dvar_pairs` docstring for more details. fig : matplotlib figure, optional Figure to plot on, by default None @@ -1047,13 +1154,13 @@ def plot_dvar_pairs( If None, no scaling is applied. colormap : str, optional Name of the colormap to use for generation colors, by default 'viridis' - cmap_label: Optional[str] = "Generation" + cmap_label: str | None = "Generation" Label for colorbar (only used when generation_mode is 'cmap') generation_mode: Literal['cmap', 'cumulative'] = 'cmap' How to handle multiple generations: 'cmap': Plot each generation separately with colors from colormap 'cumulative': Merge all selected generations into single population - single_color: Optional[str] = None + single_color: str | None = None Color to use when generation_mode is 'cumulative'. If None, uses default color from matplotlib. plot_bounds: bool = False Whether to plot bounds for the problem @@ -1090,23 +1197,23 @@ def plot_dvar_pairs( def plot_obj_animation( self, - reports: Optional[Union[int, slice, List[int], Tuple[int, int]]] = None, + reports: int | slice | list[int] | tuple[int, int] | None = None, interval: int = 200, domination_filt: Literal["all", "dominated", "non-dominated"] = "all", feasibility_filt: Literal["all", "feasible", "infeasible"] = "all", show_points: bool = True, n_pf: int = 1000, - pf_objectives: Optional[np.ndarray] = None, + pf_objectives: np.ndarray | None = None, show_attainment: bool = False, show_dominated_area: bool = False, - ref_point: Optional[Tuple[float, float]] = None, + ref_point: tuple[float, float] | None = None, ref_point_padding: float = 0.05, - legend_loc: Optional[str] = "upper right", - scale: Optional[np.ndarray] = None, + legend_loc: str | None = "upper right", + scale: np.ndarray | None = None, flip_objs: bool = False, show_names: bool = True, show_pf: bool = False, - single_color: Optional[str] = None, + single_color: str | None = None, dynamic_scaling: bool = False, cumulative: bool = False, scale_padding: float = 0.05, @@ -1116,7 +1223,7 @@ def plot_obj_animation( Parameters ---------- - reports : int, slice, List[int], or Tuple[int, int], optional + reports : int, slice, list[int], or tuple[int, int], optional Specifies which generations to animate. See `selection_to_indices` for more details. interval : int, optional Delay between frames in milliseconds, by default 200 @@ -1135,7 +1242,7 @@ def plot_obj_animation( Whether to plot the attainment surface, by default False show_dominated_area : bool, optional Plots the dominated region towards the larger values of each decision var - ref_point : Union[str, Tuple[float, float]], optional + ref_point : str | tuple[float, float], optional Where to stop plotting the dominated region / attainment surface. Must be a point to the upper right (increasing value of objectives in 3D) of all plotted points. By default, will set to right of max of each objective plus padding. @@ -1152,7 +1259,7 @@ def plot_obj_animation( Whether to show the names of the objectives if provided by population show_pf : bool, optional Whether to plot the Pareto front, by default True - single_color: Optional[str] = None + single_color: str | None = None Color to use when generation_mode is 'cumulative'. If None, uses default color from matplotlib. dynamic_scaling : bool, optional If True, axes limits will update based on each frame's data. @@ -1196,17 +1303,17 @@ def plot_obj_animation( def plot_dvar_animation( self, - reports: Optional[Union[int, slice, List[int], Tuple[int, int]]] = None, - dvars: Optional[Union[int, slice, List[int], Tuple[int, int]]] = None, + reports: int | slice | list[int] | tuple[int, int] | None = None, + dvars: int | slice | list[int] | tuple[int, int] | None = None, interval: int = 200, domination_filt: Literal["all", "dominated", "non-dominated"] = "all", feasibility_filt: Literal["all", "feasible", "infeasible"] = "all", - hist_bins: Optional[int] = None, + hist_bins: int | None = None, show_names: bool = True, - lower_bounds: Optional[np.ndarray] = None, - upper_bounds: Optional[np.ndarray] = None, - scale: Optional[np.ndarray] = None, - single_color: Optional[str] = None, + lower_bounds: np.ndarray | None = None, + upper_bounds: np.ndarray | None = None, + scale: np.ndarray | None = None, + single_color: str | None = None, plot_bounds: bool = False, dynamic_scaling: bool = False, cumulative: bool = False, @@ -1217,9 +1324,9 @@ def plot_dvar_animation( Parameters ---------- - reports : int, slice, List[int], or Tuple[int, int], optional + reports : int, slice, list[int], or tuple[int, int], optional Specifies which generations to animate. See `selection_to_indices` for more details. - dvars : int, slice, List[int], or Tuple[int, int], optional + dvars : int, slice, list[int], or tuple[int, int], optional Which decision vars to plot. See `population_dvar_pairs` docstring for more details. interval : int, optional Delay between frames in milliseconds, by default 200 @@ -1238,7 +1345,7 @@ def plot_dvar_animation( scale : array-like, optional Scale factors for each variable. Must have the same length as the number of decision vars. If None, no scaling is applied. - single_color: Optional[str] = None + single_color: str | None = None Color to use when generation_mode is 'cumulative'. If None, uses default color from matplotlib. plot_bounds: bool = False Whether to plot bounds for the problem @@ -1335,14 +1442,14 @@ class Experiment(BaseModel): used to save the data. """ - runs: List[History] + runs: list[History] name: str author: str = "" software: str = "" software_version: str = "" comment: str = "" creation_time: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) - file_version: str = "1.1.0" + file_version: str = "1.2.0" def __eq__(self, other): if not isinstance(other, Experiment): @@ -1384,6 +1491,7 @@ def from_random( pop_size: int, generate_names: bool = False, generate_obj_constraint_settings: bool = False, + generate_bounds: bool = False, ) -> "Experiment": """ Generate a randomized instance of the Experiment class. @@ -1406,6 +1514,8 @@ def from_random( Whether to include names for the decision variables, objectives, and constraints, by default False. generate_obj_constraint_settings : bool, optional Randomize the objective and constraint settings, default to minimization problem and g >= 0 constraint + generate_bounds : bool, optional + Randomize the decision variable bounds, by default the variables are left unbounded Returns ------- @@ -1422,6 +1532,7 @@ def from_random( pop_size, generate_names=generate_names, generate_obj_constraint_settings=generate_obj_constraint_settings, + generate_bounds=generate_bounds, ) for _ in range(n_histories) ] @@ -1461,7 +1572,7 @@ def save(self, fname): f.attrs["software_version"] = self.software_version f.attrs["comment"] = self.comment f.attrs["creation_time"] = self.creation_time.isoformat() - f.attrs["file_version"] = "1.1.0" + f.attrs["file_version"] = "1.2.0" f.attrs["file_format"] = "ParetoBench Multi-Objective Optimization Data" # Calculate the necessary zero padding based on the number of runs diff --git a/src/paretobench/ext/xopt.py b/src/paretobench/ext/xopt.py index 0527a0a..58fc7e2 100644 --- a/src/paretobench/ext/xopt.py +++ b/src/paretobench/ext/xopt.py @@ -15,11 +15,24 @@ # Handle xopt 2.x and 3.x constraint/objective accessor styles try: from xopt.vocs import GreaterThanConstraint - from gest_api.vocs import LessThanConstraint, MaximizeObjective, MinimizeObjective + from gest_api.vocs import ( + ContinuousVariable, + DiscreteVariable, + LessThanConstraint, + MaximizeObjective, + MinimizeObjective, + ) def _constraint_value(c): return c.value + def _variable_bounds(var): + if isinstance(var, ContinuousVariable): + return var.domain[0], var.domain[1] + elif isinstance(var, DiscreteVariable): + raise ValueError("DiscreteVariable is currently not supported by ParetoBench") + raise ValueError(f"Unrecognized variable type: {type(var)}") + def _constraint_direction(c): if isinstance(c, GreaterThanConstraint): return ">" @@ -39,6 +52,9 @@ def _objective_direction(obj): def _constraint_value(c): return c[1] + def _variable_bounds(var): + return var[0], var[1] + def _constraint_direction(c): if c[0] == "GREATER_THAN": return ">" @@ -160,6 +176,9 @@ def population_from_dataframe(df: pd.DataFrame, vocs: VOCS, errors_as_constraint Population Population object with the loaded data """ + # Get the decision variable bounds. Note that vocs.bounds is not used here as its shape changed between xopt 2.x and 3.x + var_bounds = [_variable_bounds(vocs.variables[name]) for name in vocs.variable_names] + # Get base constraints if they exist g = df[vocs.constraint_names].to_numpy() if vocs.constraints else None names_g = vocs.constraint_names @@ -190,6 +209,8 @@ def population_from_dataframe(df: pd.DataFrame, vocs: VOCS, errors_as_constraint obj_directions="".join([_objective_direction(vocs.objectives[name]) for name in vocs.objective_names]), constraint_directions="".join(constraint_directions), constraint_targets=np.array(constraint_targets), + var_lower_bounds=np.array([b[0] for b in var_bounds], dtype=np.float64), + var_upper_bounds=np.array([b[1] for b in var_bounds], dtype=np.float64), ) diff --git a/src/paretobench/misc.py b/src/paretobench/misc.py index 184ac37..f79a80c 100644 --- a/src/paretobench/misc.py +++ b/src/paretobench/misc.py @@ -204,7 +204,7 @@ def var_lower_bounds(self): @property def var_upper_bounds(self): - return np.array([[1.0, 5.0]]) + return np.array([1.0, 5.0]) @property def reference(self): diff --git a/src/paretobench/plotting/history.py b/src/paretobench/plotting/history.py index dd9746e..b65cfaa 100644 --- a/src/paretobench/plotting/history.py +++ b/src/paretobench/plotting/history.py @@ -298,6 +298,7 @@ def history_dvar_pairs( hist_bins=hist_bins, show_names=show_names, scale=scale, + plot_bounds=plot_bounds, ) if generation_mode == "cumulative": @@ -336,12 +337,14 @@ def history_dvar_pairs( # Only plot bounds on the last iteration if requested if plot_idx == len(indices) - 1: + plot_settings["plot_bounds"] = plot_bounds if plot_bounds and user_specified_bounds: plot_settings["lower_bounds"] = lower_bounds plot_settings["upper_bounds"] = upper_bounds elif plot_bounds and history.problem is not None: plot_settings["problem"] = history.problem else: + plot_settings["plot_bounds"] = False plot_settings["problem"] = None plot_settings["lower_bounds"] = None plot_settings["upper_bounds"] = None diff --git a/src/paretobench/plotting/population.py b/src/paretobench/plotting/population.py index fbd8ccd..4a0b6f2 100644 --- a/src/paretobench/plotting/population.py +++ b/src/paretobench/plotting/population.py @@ -8,8 +8,7 @@ from ..containers import Population from ..exceptions import EmptyPopulationError, NoDecisionVarsError, NoObjectivesError -from ..problem import Problem, ProblemWithFixedPF, ProblemWithPF -from ..utils import get_problem_from_obj_or_str +from ..problem import Problem, ProblemWithFixedPF, ProblemWithPF, get_problem_from_obj_or_str from .attainment import compute_attainment_surface_2d, compute_attainment_surface_3d from .utils import get_per_point_settings_population, alpha_scatter, selection_to_indices @@ -291,6 +290,28 @@ def population_obj_scatter( return fig, ax +def _draw_bound(line_fn, bounds, idx, scale, props): + """ + Draws one decision variable bound onto an axis. Unset and infinite bounds are skipped as they have no location + on the plot and would ruin the axis limits. + + Parameters + ---------- + line_fn : callable + The axis method used to draw the line (`ax.axvline` or `ax.axhline`). + bounds : array-like or None + The lower or upper bounds of all decision variables. + idx : int + Index of the decision variable being drawn. + scale : float + Scale factor applied to this decision variable. + props : dict + Line properties passed through to `line_fn`. + """ + if bounds is not None and np.isfinite(bounds[idx]): + line_fn(scale * bounds[idx], **props) + + def population_dvar_pairs( population: Population, dvars: Optional[Union[int, slice, List[int], Tuple[int, int]]] = None, @@ -305,6 +326,7 @@ def population_dvar_pairs( upper_bounds: Optional[np.ndarray] = None, color: Optional[str] = None, scale: Optional[np.ndarray] = None, + plot_bounds: bool = True, ): """ Creates a pairs plot (scatter matrix) showing correlations between decision variables @@ -332,14 +354,16 @@ def population_dvar_pairs( problem : str/Problem, optional The problem for plotting decision variable bounds lower_bounds : array-like, optional - Lower bounds for each decision variable + Lower bounds for each decision variable. Defaults to the bounds carried by the population. upper_bounds : array-like, optional - Upper bounds for each decision variable + Upper bounds for each decision variable. Defaults to the bounds carried by the population. color : str, optional What color should we use for the points. Defaults to selecting from matplotlib color cycler scale : array-like, optional Scale factors for each variable. Must have the same length as the number of decision vars. If None, no scaling is applied. + plot_bounds : bool, optional + Whether to plot the decision variable bounds, by default True. Infinite bounds are not plotted. Returns ------- @@ -367,10 +391,6 @@ def population_dvar_pairs( var_indices = np.array(selection_to_indices(dvars, population.n)) n_vars = len(var_indices) - # Default, don't show bounds - lower_bounds = None - upper_bounds = None - # Handle user specified problem if problem is not None: if (lower_bounds is not None) or (upper_bounds is not None): @@ -383,6 +403,16 @@ def population_dvar_pairs( lower_bounds = problem.var_lower_bounds upper_bounds = problem.var_upper_bounds + # Fall back onto the bounds carried by the population itself + elif plot_bounds and (lower_bounds is None) and (upper_bounds is None): + lower_bounds = population.var_lower_bounds + upper_bounds = population.var_upper_bounds + + # The bounds are not plotted when the user asks us not to + if not plot_bounds: + lower_bounds = None + upper_bounds = None + # Validate and convert bounds to numpy arrays if provided if lower_bounds is not None: lower_bounds = np.asarray(lower_bounds) @@ -484,10 +514,8 @@ def population_dvar_pairs( base_color = patches[0].get_facecolor() # Add vertical bound lines to histograms - if lower_bounds is not None: - ax.axvline(scale[var_indices[i]] * lower_bounds[var_indices[i]], **bound_props) - if upper_bounds is not None: - ax.axvline(scale[var_indices[i]] * upper_bounds[var_indices[i]], **bound_props) + _draw_bound(ax.axvline, lower_bounds, var_indices[i], scale[var_indices[i]], bound_props) + _draw_bound(ax.axvline, upper_bounds, var_indices[i], scale[var_indices[i]], bound_props) # Off-diagonal plots (scatter plots) else: @@ -505,12 +533,10 @@ def population_dvar_pairs( base_color = scatter.get_facecolor()[0] # Get the color that matplotlib assigned # Add bound lines to scatter plots - if lower_bounds is not None: - ax.axvline(scale[var_indices[j]] * lower_bounds[var_indices[j]], **bound_props) # x-axis bound - ax.axhline(scale[var_indices[i]] * lower_bounds[var_indices[i]], **bound_props) # y-axis bound - if upper_bounds is not None: - ax.axvline(scale[var_indices[j]] * upper_bounds[var_indices[j]], **bound_props) # x-axis bound - ax.axhline(scale[var_indices[i]] * upper_bounds[var_indices[i]], **bound_props) # y-axis bound + _draw_bound(ax.axvline, lower_bounds, var_indices[j], scale[var_indices[j]], bound_props) + _draw_bound(ax.axhline, lower_bounds, var_indices[i], scale[var_indices[i]], bound_props) + _draw_bound(ax.axvline, upper_bounds, var_indices[j], scale[var_indices[j]], bound_props) + _draw_bound(ax.axhline, upper_bounds, var_indices[i], scale[var_indices[i]], bound_props) if i == n_vars - 1: ax.set_xlabel(var_names[j]) if j == 0: diff --git a/src/paretobench/problem.py b/src/paretobench/problem.py index 2ce5fb3..ed39105 100644 --- a/src/paretobench/problem.py +++ b/src/paretobench/problem.py @@ -1,6 +1,7 @@ import numpy as np from pydantic import BaseModel +from .containers import Population from .exceptions import DeserializationError, InputError from .factory import create_problem from .simple_serialize import dumps, loads @@ -45,8 +46,7 @@ def __call__(self, x: np.ndarray, check_bounds=True): raise InputError(msg) if check_bounds and ((x > self.var_upper_bounds).all() or (x < self.var_lower_bounds).all()): raise InputError("Input lies outside of problem bounds.") - pop = self._call(x[None, :]) - pop.x = np.reshape(x, (1, -1)) + x = np.reshape(x, (1, -1)) # If batched input is used elif len(x.shape) == 2: @@ -57,15 +57,23 @@ def __call__(self, x: np.ndarray, check_bounds=True): raise InputError(msg) if check_bounds and ((x > self.var_upper_bounds).all() or (x < self.var_lower_bounds).all()): raise InputError("Input lies outside of problem bounds.") - pop = self._call(x) - pop.x = x # If user provided something not usable else: raise ValueError(f"Incompatible shape of input array x: {x.shape}") - # Set the decision variables - return pop + # Attach the decision variables and the problem's bounds to the evaluated population. These must be set together + # because the bounds are validated against the number of decision variables, so assigning them one at a time + # leaves the population in a state which does not validate. + pop = self._call(x) + return Population.model_validate( + { + **dict(pop), + "x": x, + "var_lower_bounds": np.asarray(self.var_lower_bounds, dtype=np.float64), + "var_upper_bounds": np.asarray(self.var_upper_bounds, dtype=np.float64), + } + ) def _call(self, x: np.ndarray): """ @@ -193,6 +201,33 @@ def __str__(self): return self.__repr__() +def get_problem_from_obj_or_str(obj_or_str: "str | Problem") -> "Problem": + """Convert input to Problem instance. + + Parameters + ---------- + obj_or_str : Problem or str + Input to convert. If already a Problem instance, returns as-is. + If string, creates Problem from line format. + + Returns + ------- + Problem + The resulting Problem instance. + + Raises + ------ + ValueError + If input is neither Problem nor str type. + """ + if isinstance(obj_or_str, Problem): + return obj_or_str + elif isinstance(obj_or_str, str): + return Problem.from_line_fmt(obj_or_str) + else: + raise ValueError(f"Unrecognized input type: {type(obj_or_str)}") + + class ProblemWithPF: """ Mixin class for problems with a defined Pareto front where you can request a certain number of points from it. diff --git a/src/paretobench/utils.py b/src/paretobench/utils.py index 0bd135d..a722e56 100644 --- a/src/paretobench/utils.py +++ b/src/paretobench/utils.py @@ -1,10 +1,7 @@ from itertools import combinations, chain, count from math import comb -from typing import Union import numpy as np -from .problem import Problem - def get_betas(m, p): """ @@ -171,33 +168,6 @@ def weighted_chunk_sizes(n, weights): return ns -def get_problem_from_obj_or_str(obj_or_str: Union[str, Problem]) -> Problem: - """Convert input to Problem instance. - - Parameters - ---------- - obj_or_str : Problem or str - Input to convert. If already a Problem instance, returns as-is. - If string, creates Problem from line format. - - Returns - ------- - Problem - The resulting Problem instance. - - Raises - ------ - ValueError - If input is neither Problem nor str type. - """ - if isinstance(obj_or_str, Problem): - return obj_or_str - elif isinstance(obj_or_str, str): - return Problem.from_line_fmt(obj_or_str) - else: - raise ValueError(f"Unrecognized input type: {type(obj_or_str)}") - - def binary_str_to_numpy(ss, pos_char, neg_char): """ Convert the characters of the string ss into a numpy array with +1 being wherever diff --git a/tests/ext/test_xopt.py b/tests/ext/test_xopt.py index ed1427d..0efd0d5 100644 --- a/tests/ext/test_xopt.py +++ b/tests/ext/test_xopt.py @@ -182,6 +182,11 @@ def df_comp(df1, df2): assert all(tp.constraint_targets == [0.0, 0.0]) assert tp.fevals == (idx + 1) * population_size + # Confirm the decision variable bounds came from the VOCS + vocs_bounds = [_variable_bounds(tnk_vocs.variables[name]) for name in tnk_vocs.variable_names] + np.testing.assert_allclose(tp.var_lower_bounds, [b[0] for b in vocs_bounds]) + np.testing.assert_allclose(tp.var_upper_bounds, [b[1] for b in vocs_bounds]) + # Confirm data is correct df_comp(rx, pd.DataFrame(tp.x, columns=tp.names_x)) rf.columns = [x.removeprefix("objective_") for x in rf.columns] diff --git a/tests/generate_file_version_data.py b/tests/generate_file_version_data.py index 18b38a9..1ebdce7 100644 --- a/tests/generate_file_version_data.py +++ b/tests/generate_file_version_data.py @@ -39,6 +39,7 @@ def make_experiment(): pop_size=25, generate_names=True, generate_obj_constraint_settings=True, + generate_bounds=True, ) run.problem = problem runs.append(run) @@ -84,12 +85,19 @@ def main(): ) parser.add_argument("--out-dir", type=Path, default=DEFAULT_OUT_DIR, help="directory to save the file into") parser.add_argument("--force", action="store_true", help="overwrite an existing file for this version") - parser.add_argument("--manifest", type=Path, help="rewrite the manifest of an existing file and exit") + parser.add_argument( + "--refresh-manifests", + type=Path, + nargs="*", + help="rewrite the manifests of files which already exist, defaulting to every file in the output directory", + ) args = parser.parse_args() - # Refresh the manifest of a file which already exists (used to bootstrap files saved by older versions) - if args.manifest is not None: - print(f"Wrote {write_manifest(args.manifest)}") + # Rewrite the manifests of files which already exist. Needed whenever the contents of a manifest change, such as + # when a new field is added to the containers, and to bootstrap the manifests of files saved by older versions. + if args.refresh_manifests is not None: + for path in args.refresh_manifests or sorted(args.out_dir.glob("*.h5")): + print(f"Wrote {write_manifest(path)}") return # Save the data, then name the file after the version which actually ended up in it diff --git a/tests/test_containers.py b/tests/test_containers.py index 8026072..7557eb5 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -1,4 +1,5 @@ from pydantic import ValidationError +import h5py import numpy as np import os import pytest @@ -8,7 +9,8 @@ @pytest.mark.parametrize("generate_names", [False, True]) -def test_experiment_save_load(generate_names): +@pytest.mark.parametrize("generate_bounds", [False, True]) +def test_experiment_save_load(generate_names, generate_bounds): """ Make a randomized experiment, save it to disk, load it, and then confirm everything matches. """ @@ -22,6 +24,7 @@ def test_experiment_save_load(generate_names): pop_size=50, generate_names=generate_names, generate_obj_constraint_settings=True, + generate_bounds=generate_bounds, ) # Use a temporary directory to save the file @@ -266,6 +269,117 @@ def test_field_assignment_validation(): pop.x = np.random.random((2)) +def test_save_load_var_bounds_backwards_compatible(): + """ + Files written before the bounds were added (file version 1.2.0) load as unbounded populations. + """ + experiment = Experiment.from_random( + n_histories=2, + n_populations=3, + n_objectives=2, + n_decision_vars=4, + n_constraints=1, + pop_size=8, + generate_bounds=True, + ) + + with tempfile.TemporaryDirectory() as tmpdir: + file_path = os.path.join(tmpdir, "test.h5") + experiment.save(file_path) + + # Strip the bounds attributes to imitate a file written by an older version of ParetoBench + with h5py.File(file_path, mode="r+") as f: + f.attrs["file_version"] = "1.1.0" + for run_grp in [f[k] for k in f if k.startswith("run_")]: + del run_grp["x"].attrs["lower_bounds"] + del run_grp["x"].attrs["upper_bounds"] + + loaded_experiment = Experiment.load(file_path) + assert loaded_experiment.file_version == "1.1.0" + for run in loaded_experiment.runs: + for report in run.reports: + np.testing.assert_array_equal(report.var_lower_bounds, np.full(report.n, -np.inf)) + np.testing.assert_array_equal(report.var_upper_bounds, np.full(report.n, np.inf)) + + +def test_save_load_no_decision_vars(): + """ + Populations without decision variables load with empty bounds arrays. + """ + experiment = Experiment.from_random( + n_histories=1, + n_populations=2, + n_objectives=2, + n_decision_vars=0, + n_constraints=1, + pop_size=8, + ) + + with tempfile.TemporaryDirectory() as tmpdir: + file_path = os.path.join(tmpdir, "test.h5") + experiment.save(file_path) + loaded_experiment = Experiment.load(file_path) + + assert experiment == loaded_experiment + assert loaded_experiment.runs[0].reports[0].var_lower_bounds.shape == (0,) + + +def test_default_var_bounds(): + """ + Populations created without bounds treat the decision variables as unbounded. + """ + pop = Population(x=np.random.random((16, 4)), f=np.random.random((16, 2))) + np.testing.assert_array_equal(pop.var_lower_bounds, np.full(4, -np.inf)) + np.testing.assert_array_equal(pop.var_upper_bounds, np.full(4, np.inf)) + + # The default bounds must survive the operations which pass them along + assert (pop + pop).var_lower_bounds.shape == (4,) + np.testing.assert_array_equal(pop[:4].var_upper_bounds, np.full(4, np.inf)) + assert pop == pop[:] + + +def test_var_bounds_validation(): + """ + Bounds must be sized to the decision variables and ordered lower <= upper. + """ + x = np.random.random((16, 3)) + + with pytest.raises(ValidationError, match="Length of var_lower_bounds must match number of decision variables"): + Population(x=x, var_lower_bounds=np.zeros(2)) + + with pytest.raises(ValidationError, match="var_upper_bounds must be 1D"): + Population(x=x, var_upper_bounds=np.ones((1, 3))) + + with pytest.raises(ValidationError, match=r"lower > upper at indices \[1\]"): + Population(x=x, var_lower_bounds=np.array([0.0, 1.0, 0.0]), var_upper_bounds=np.array([1.0, 0.0, 1.0])) + + # Equal lower and upper bounds are allowed (degenerate variable) + Population(x=x, var_lower_bounds=np.zeros(3), var_upper_bounds=np.zeros(3)) + + +def test_var_bounds_consistency(): + """ + Adding populations requires matching bounds and carries them into the result. + """ + kwargs = dict(f=np.random.random((16, 2)), var_lower_bounds=np.zeros(3), var_upper_bounds=np.ones(3)) + pop1 = Population(x=np.random.random((16, 3)), **kwargs) + pop2 = Population(x=np.random.random((16, 3)), **kwargs) + np.testing.assert_array_equal((pop1 + pop2).var_upper_bounds, np.ones(3)) + + pop3 = Population( + x=np.random.random((16, 3)), + f=np.random.random((16, 2)), + var_lower_bounds=np.zeros(3), + var_upper_bounds=2 * np.ones(3), + ) + with pytest.raises(ValueError, match="var_upper_bounds are inconsistent between populations"): + pop1 + pop3 + + # Histories must also have consistent bounds across their reports + with pytest.raises(ValidationError, match="Inconsistent var_upper_bounds in reports"): + History(reports=[pop1, pop3], problem="") + + def test_overwrite(): # Create a randomized Experiment object experiment1 = Experiment.from_random( diff --git a/tests/test_data/file_versions/paretobench_file_format_v1.0.0.json b/tests/test_data/file_versions/paretobench_file_format_v1.0.0.json index e6fccae..5848279 100644 --- a/tests/test_data/file_versions/paretobench_file_format_v1.0.0.json +++ b/tests/test_data/file_versions/paretobench_file_format_v1.0.0.json @@ -23,6 +23,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "3b2d74eed2b6600433de939b181f2856fd70cff92d1e32909ac7ac88a1f77c28" }, { @@ -39,6 +53,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "119795280e0e1fb558ecef619d4b6083f4aecbcc34cee074fce3fc6954aeae39" }, { @@ -55,6 +83,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "b37443ac45a3bde81bca6686a6944783486812472181ac3619e2ee855dec6d59" }, { @@ -71,6 +113,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "1630ebf46c0d910fff487a13b95c3072fd90f0ff1fe24b97c36efc63bca838b0" }, { @@ -87,6 +143,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "dd1e5b71e3d6215173745aae9e64219982ff4062d5a8345c977c20e649b5a5cb" }, { @@ -103,6 +173,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "be66f8a208a6bf59dc4478761cfac501d598fe3871587234d3cdeb1b275732e5" }, { @@ -119,6 +203,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "f991d73ee8b4949445a5f91f3dadde91552dd973a3f67bf833031a3b144241d2" }, { @@ -135,6 +233,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "09ab4b8ea994852ca3fa95538e26ee13239892b70c3c985ba1976950ee710cc9" } ] @@ -157,6 +269,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "5129b12bc092720878f65ea8e8e64c9c8e10bc6525b860065f50f205af1aaa30" }, { @@ -173,6 +299,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c80488e2e77c8c7fb07e192a8f9cd13b42f72f11d0fa163d719bc88f2bf1d4b7" }, { @@ -189,6 +329,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "ee1f2f7ef29d5187d13ee669f998237cafc28ea07ed456c5aa3a0d0bdf515e8c" }, { @@ -205,6 +359,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "3ece64d9ccb0fb86003eaafa4f6a7856af7be2adb4e245d9ea2c853f3d71e3d8" }, { @@ -221,6 +389,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c0c5551a141602e30a80a6796623b695849388591c00fc82c8b34af0520fb661" }, { @@ -237,6 +419,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "42625c5e8f003a3d4a1b93de25a9b8fd26b3e2ab9886fa3a97269bcaf8bd02a2" }, { @@ -253,6 +449,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "d86d9f3cb6b714bf93d17bd33f7ee0a74498b52698e921a2f0438cf3a07c2df7" }, { @@ -269,6 +479,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "40aaaf70ad4952885313cd34311f415c24465187959559f82fe86f3aab5b825f" } ] @@ -291,6 +515,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "6bbc30fdb0e0c1d7ec230cf6dc41d161fd6865f24c6df5df9aee491f36051a67" }, { @@ -307,6 +545,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "8651c618c24b08877aa5dcc7ef1ec4cf8d445b25c1dccc640bbe92ad7f4a16c9" }, { @@ -323,6 +575,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "1ceb5e1607a108f9ab4b93837b9901ddede725dda4d8502d713dd706dbbb6825" }, { @@ -339,6 +605,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "8ab0082734025e52fbfe729d76979a91b9c83e39c31320dbb264176f90e38514" }, { @@ -355,6 +635,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "22837213bf2a410f53800c2a28a20a6e650916366556d5f5bd47f2dfd13e7853" }, { @@ -371,6 +665,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "0acad307f3e412fbffc9c901a205f8931d569841749089bc8956539d7c24a281" }, { @@ -387,6 +695,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c2be26ae7b20916e87308eaaab9fffc0462d05f28fe74bdc6c61eac8b1532991" }, { @@ -403,6 +725,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "df3e9b06e4777cfbadd0412e78484785b22d667a11dc906b1659f49b79894f4e" } ] @@ -425,6 +761,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "240594fa871c1c47eaa65f8d603b06fad4a635e8a45ddc31a0029e2a6b2d4d1b" }, { @@ -441,6 +791,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "5ff66c434ffbd1aa9b95d04dd3bd39ce9403d91d0f1034ed30b9cfef08de24b7" }, { @@ -457,6 +821,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "20d33df82efff7fbfcb1657e6fa3bfeac1282030eae63e31511481710a1b4926" }, { @@ -473,6 +851,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "d86606ca44168d2515236f57472e6b1ae3731eb10ef7c201567682043228db3b" }, { @@ -489,6 +881,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "6fdfff6603e039d843b46a1cfaf5b217b8c8d51c7978cbcfbbc541b1a5b8aa81" }, { @@ -505,6 +911,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "0a6febc59f3123b6f80343dc5b424dc154f8a53d8bb1948e0c2b56f5eff95c24" }, { @@ -521,6 +941,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c22b349b1a39dda308828bd41499db3b9fef874516ce53e0e08a4af716d9ff9c" }, { @@ -537,6 +971,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "270bcb920898d79b9fd67c9a5c89133bf40a76366641cf3d0227ed42259cef19" } ] @@ -559,6 +1007,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "936910b0844695c0bdb24f530781a2d6c2436c76d19958de2f391c13648a9da5" }, { @@ -575,6 +1037,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "1d5b2eff103a96a2046b11f9e416750346ebb1359bd9e376ea1f24893be184b2" }, { @@ -591,6 +1067,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "55d78e8bf10287f6857049b720c1f83f8a81db87521dc44a8a3b14a165150e7f" }, { @@ -607,6 +1097,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "5172e91510bc1f18b8f47edc1e13604ba0be6d7e2e7f2bad3a49efec6d9fbe51" }, { @@ -623,6 +1127,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "de77677aa32987444792688788dec6c1bc7d3c2509397e1fa9766627625877fb" }, { @@ -639,6 +1157,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "733ce3a1c45dcd1f511cd507cdf708d53b2809be50e60cf3a1b0260d47fd3f54" }, { @@ -655,6 +1187,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "a1362da78318ba8f67e36d6cd22d3e50b2345721cc6d308a6dc08e690cc7f8db" }, { @@ -671,6 +1217,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "ef9d2bb572962c77dd566adf13ae1ef7d32400b064adbce3f8b023f14c612225" } ] @@ -693,6 +1253,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "e6b663e84e7955e5ddf753f9b0d55d89b31305e5c5eb17ae0bdb10dec010e709" }, { @@ -709,6 +1283,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "45edf82f5a101789bfa222ed329243abcc781ce1a21bbd403ead41260543a9a6" }, { @@ -725,6 +1313,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "fab599d313a85a247de1647da245c6a43b36833cb171776ea21afcbf1bf0d585" }, { @@ -741,6 +1343,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "8466b5596f0214a13d971f6552ef11bf90295c22e4c40dd631c659660fe95597" }, { @@ -757,6 +1373,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "9a395681bfa39ffdf8de35f84efff9f7aea3e78922fc83a4d592b426894eb3f1" }, { @@ -773,6 +1403,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c19e3f2d09a32777ce4dd264d88d9668d9c3a4d6213f8685b1a75651bf0d8eba" }, { @@ -789,6 +1433,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "1103dbced7246f9385555edbd1058382a322f2e52bf2a5909543ac5233193c3b" }, { @@ -805,6 +1463,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "29b0adcb029240572c1254f6184dc30a4a447ee011c62a9fc065af4d420fb715" } ] diff --git a/tests/test_data/file_versions/paretobench_file_format_v1.1.0.json b/tests/test_data/file_versions/paretobench_file_format_v1.1.0.json index bfbb218..5966078 100644 --- a/tests/test_data/file_versions/paretobench_file_format_v1.1.0.json +++ b/tests/test_data/file_versions/paretobench_file_format_v1.1.0.json @@ -23,6 +23,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "3b2d74eed2b6600433de939b181f2856fd70cff92d1e32909ac7ac88a1f77c28" }, { @@ -39,6 +53,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "119795280e0e1fb558ecef619d4b6083f4aecbcc34cee074fce3fc6954aeae39" }, { @@ -55,6 +83,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "b37443ac45a3bde81bca6686a6944783486812472181ac3619e2ee855dec6d59" }, { @@ -71,6 +113,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "1630ebf46c0d910fff487a13b95c3072fd90f0ff1fe24b97c36efc63bca838b0" }, { @@ -87,6 +143,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "dd1e5b71e3d6215173745aae9e64219982ff4062d5a8345c977c20e649b5a5cb" }, { @@ -103,6 +173,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "be66f8a208a6bf59dc4478761cfac501d598fe3871587234d3cdeb1b275732e5" }, { @@ -119,6 +203,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "f991d73ee8b4949445a5f91f3dadde91552dd973a3f67bf833031a3b144241d2" }, { @@ -135,6 +233,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "09ab4b8ea994852ca3fa95538e26ee13239892b70c3c985ba1976950ee710cc9" } ] @@ -157,6 +269,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "5129b12bc092720878f65ea8e8e64c9c8e10bc6525b860065f50f205af1aaa30" }, { @@ -173,6 +299,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c80488e2e77c8c7fb07e192a8f9cd13b42f72f11d0fa163d719bc88f2bf1d4b7" }, { @@ -189,6 +329,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "ee1f2f7ef29d5187d13ee669f998237cafc28ea07ed456c5aa3a0d0bdf515e8c" }, { @@ -205,6 +359,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "3ece64d9ccb0fb86003eaafa4f6a7856af7be2adb4e245d9ea2c853f3d71e3d8" }, { @@ -221,6 +389,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c0c5551a141602e30a80a6796623b695849388591c00fc82c8b34af0520fb661" }, { @@ -237,6 +419,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "42625c5e8f003a3d4a1b93de25a9b8fd26b3e2ab9886fa3a97269bcaf8bd02a2" }, { @@ -253,6 +449,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "d86d9f3cb6b714bf93d17bd33f7ee0a74498b52698e921a2f0438cf3a07c2df7" }, { @@ -269,6 +479,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "40aaaf70ad4952885313cd34311f415c24465187959559f82fe86f3aab5b825f" } ] @@ -291,6 +515,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "6bbc30fdb0e0c1d7ec230cf6dc41d161fd6865f24c6df5df9aee491f36051a67" }, { @@ -307,6 +545,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "8651c618c24b08877aa5dcc7ef1ec4cf8d445b25c1dccc640bbe92ad7f4a16c9" }, { @@ -323,6 +575,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "1ceb5e1607a108f9ab4b93837b9901ddede725dda4d8502d713dd706dbbb6825" }, { @@ -339,6 +605,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "8ab0082734025e52fbfe729d76979a91b9c83e39c31320dbb264176f90e38514" }, { @@ -355,6 +635,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "22837213bf2a410f53800c2a28a20a6e650916366556d5f5bd47f2dfd13e7853" }, { @@ -371,6 +665,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "0acad307f3e412fbffc9c901a205f8931d569841749089bc8956539d7c24a281" }, { @@ -387,6 +695,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c2be26ae7b20916e87308eaaab9fffc0462d05f28fe74bdc6c61eac8b1532991" }, { @@ -403,6 +725,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "df3e9b06e4777cfbadd0412e78484785b22d667a11dc906b1659f49b79894f4e" } ] @@ -425,6 +761,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "240594fa871c1c47eaa65f8d603b06fad4a635e8a45ddc31a0029e2a6b2d4d1b" }, { @@ -441,6 +791,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "5ff66c434ffbd1aa9b95d04dd3bd39ce9403d91d0f1034ed30b9cfef08de24b7" }, { @@ -457,6 +821,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "20d33df82efff7fbfcb1657e6fa3bfeac1282030eae63e31511481710a1b4926" }, { @@ -473,6 +851,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "d86606ca44168d2515236f57472e6b1ae3731eb10ef7c201567682043228db3b" }, { @@ -489,6 +881,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "6fdfff6603e039d843b46a1cfaf5b217b8c8d51c7978cbcfbbc541b1a5b8aa81" }, { @@ -505,6 +911,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "0a6febc59f3123b6f80343dc5b424dc154f8a53d8bb1948e0c2b56f5eff95c24" }, { @@ -521,6 +941,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c22b349b1a39dda308828bd41499db3b9fef874516ce53e0e08a4af716d9ff9c" }, { @@ -537,6 +971,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "270bcb920898d79b9fd67c9a5c89133bf40a76366641cf3d0227ed42259cef19" } ] @@ -559,6 +1007,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "936910b0844695c0bdb24f530781a2d6c2436c76d19958de2f391c13648a9da5" }, { @@ -575,6 +1037,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "1d5b2eff103a96a2046b11f9e416750346ebb1359bd9e376ea1f24893be184b2" }, { @@ -591,6 +1067,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "55d78e8bf10287f6857049b720c1f83f8a81db87521dc44a8a3b14a165150e7f" }, { @@ -607,6 +1097,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "5172e91510bc1f18b8f47edc1e13604ba0be6d7e2e7f2bad3a49efec6d9fbe51" }, { @@ -623,6 +1127,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "de77677aa32987444792688788dec6c1bc7d3c2509397e1fa9766627625877fb" }, { @@ -639,6 +1157,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "733ce3a1c45dcd1f511cd507cdf708d53b2809be50e60cf3a1b0260d47fd3f54" }, { @@ -655,6 +1187,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "a1362da78318ba8f67e36d6cd22d3e50b2345721cc6d308a6dc08e690cc7f8db" }, { @@ -671,6 +1217,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "ef9d2bb572962c77dd566adf13ae1ef7d32400b064adbce3f8b023f14c612225" } ] @@ -693,6 +1253,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "e6b663e84e7955e5ddf753f9b0d55d89b31305e5c5eb17ae0bdb10dec010e709" }, { @@ -709,6 +1283,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "45edf82f5a101789bfa222ed329243abcc781ce1a21bbd403ead41260543a9a6" }, { @@ -725,6 +1313,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "fab599d313a85a247de1647da245c6a43b36833cb171776ea21afcbf1bf0d585" }, { @@ -741,6 +1343,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "8466b5596f0214a13d971f6552ef11bf90295c22e4c40dd631c659660fe95597" }, { @@ -757,6 +1373,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "9a395681bfa39ffdf8de35f84efff9f7aea3e78922fc83a4d592b426894eb3f1" }, { @@ -773,6 +1403,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "c19e3f2d09a32777ce4dd264d88d9668d9c3a4d6213f8685b1a75651bf0d8eba" }, { @@ -789,6 +1433,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "1103dbced7246f9385555edbd1058382a322f2e52bf2a5909543ac5233193c3b" }, { @@ -805,6 +1463,20 @@ "names_x": null, "obj_directions": "--", "pop_size": 50, + "var_lower_bounds": [ + -Infinity, + -Infinity, + -Infinity, + -Infinity, + -Infinity + ], + "var_upper_bounds": [ + Infinity, + Infinity, + Infinity, + Infinity, + Infinity + ], "x_sha256": "29b0adcb029240572c1254f6184dc30a4a447ee011c62a9fc065af4d420fb715" } ] diff --git a/tests/test_data/file_versions/paretobench_file_format_v1.2.0.h5 b/tests/test_data/file_versions/paretobench_file_format_v1.2.0.h5 new file mode 100644 index 0000000..81496aa Binary files /dev/null and b/tests/test_data/file_versions/paretobench_file_format_v1.2.0.h5 differ diff --git a/tests/test_data/file_versions/paretobench_file_format_v1.2.0.json b/tests/test_data/file_versions/paretobench_file_format_v1.2.0.json new file mode 100644 index 0000000..e595d38 --- /dev/null +++ b/tests/test_data/file_versions/paretobench_file_format_v1.2.0.json @@ -0,0 +1,560 @@ +{ + "author": "ParetoBench test suite", + "comment": "Synthetic data used by the file format regression tests", + "creation_time": "2025-01-01T00:00:00+00:00", + "file_version": "1.2.0", + "name": "file_format_regression", + "runs": [ + { + "metadata": { + "author": "1PJSO", + "date": "2022-08-21", + "description": "Randomly generated metadata", + "version": 2.567597178069545 + }, + "problem": "ZDT1 (n=4)", + "reports": [ + { + "constraint_directions": ">>", + "constraint_targets": [ + 0.26461952968972313, + 0.575533888310394 + ], + "f_sha256": "45a284d7ff1f7adf3bf783a60a9cbaa018eab491be73ae29faaec2a470ff4b1b", + "fevals": 25, + "g_sha256": "bceb32f24b52ee5ef02939578c8cca0739053dbbcedf665b5b4510ff3292b010", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+--", + "pop_size": 25, + "var_lower_bounds": [ + -0.19490100382675557, + -0.7412316659101856, + -0.9369278798585663, + -0.6592678664279883 + ], + "var_upper_bounds": [ + 1.7249940940432136, + 1.1644884116937722, + 1.7482239682739769, + 1.4973568393326229 + ], + "x_sha256": "299d795b83d31661d68ced7b2da76e9c82025cb50a1542e448fdd669dedfa0a6" + }, + { + "constraint_directions": ">>", + "constraint_targets": [ + 0.26461952968972313, + 0.575533888310394 + ], + "f_sha256": "3599839cff2cf01168f040c60c81b1fb4c21b23b850b7a2926a1f9a5fee4e29f", + "fevals": 50, + "g_sha256": "7ed800375c3714ce6675e458a21a6bc95a465304cbc5dfcb8ad8595e98ccb6e1", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+--", + "pop_size": 25, + "var_lower_bounds": [ + -0.19490100382675557, + -0.7412316659101856, + -0.9369278798585663, + -0.6592678664279883 + ], + "var_upper_bounds": [ + 1.7249940940432136, + 1.1644884116937722, + 1.7482239682739769, + 1.4973568393326229 + ], + "x_sha256": "f4079004496a41db3fbb31a7444767cebcc5e4e8f31d35b88f18446a11f891a4" + }, + { + "constraint_directions": ">>", + "constraint_targets": [ + 0.26461952968972313, + 0.575533888310394 + ], + "f_sha256": "e9d0ce4478f37e940a9d9c1e0ad10eaf8150e33c5b6be0f6da37f300a934e251", + "fevals": 75, + "g_sha256": "1f263edc7fcc99491744a795b6c87206f335d050148f9b8e634c5e8c8f862ae1", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+--", + "pop_size": 25, + "var_lower_bounds": [ + -0.19490100382675557, + -0.7412316659101856, + -0.9369278798585663, + -0.6592678664279883 + ], + "var_upper_bounds": [ + 1.7249940940432136, + 1.1644884116937722, + 1.7482239682739769, + 1.4973568393326229 + ], + "x_sha256": "0794101e8ae726b2e44c5c7b36bcdde3a56b71176709d581decc47b4573cc521" + }, + { + "constraint_directions": ">>", + "constraint_targets": [ + 0.26461952968972313, + 0.575533888310394 + ], + "f_sha256": "51d5c48f6e3811959e703f3b12820de79a906ca308374d6f31f1843e6aac4120", + "fevals": 100, + "g_sha256": "22042afb2278d9a5e6407d031bfb6f9a37fdea6f6fd106450aae48bd65ee8615", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+--", + "pop_size": 25, + "var_lower_bounds": [ + -0.19490100382675557, + -0.7412316659101856, + -0.9369278798585663, + -0.6592678664279883 + ], + "var_upper_bounds": [ + 1.7249940940432136, + 1.1644884116937722, + 1.7482239682739769, + 1.4973568393326229 + ], + "x_sha256": "680dd84014c285805436d17cf40b17f2150326c745189599f103163a01ce7222" + } + ] + }, + { + "metadata": { + "author": "SK1WJ", + "date": "2024-03-19", + "description": "Randomly generated metadata", + "version": 2.81949251193648 + }, + "problem": "CTP1 (n=4)", + "reports": [ + { + "constraint_directions": "<>", + "constraint_targets": [ + 0.607741847385851, + 0.4877645594977067 + ], + "f_sha256": "19a5a9571032626303fc6d1c9c1480c348ed10fa9dbf898174586f2137127af2", + "fevals": 25, + "g_sha256": "52f0669483cf200f107551b33af830e70c9b0c25f85df49aea6560cc86a2a707", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+++", + "pop_size": 25, + "var_lower_bounds": [ + -0.01331611324646731, + -0.6062619062265414, + -0.9890880753568537, + -0.818101050024029 + ], + "var_upper_bounds": [ + 1.340604643884352, + 1.1520470250444221, + 1.784058614467154, + 1.7439378206317415 + ], + "x_sha256": "c720a83b3a64c9cecc82fd4e11210e1b61550a76af11ec996a0e89e0020e3833" + }, + { + "constraint_directions": "<>", + "constraint_targets": [ + 0.607741847385851, + 0.4877645594977067 + ], + "f_sha256": "37219e5328d5b2d206078ea266e8c3306bb1bb95047bc262bdbd1dbb5899a8e4", + "fevals": 50, + "g_sha256": "6f22bedd12aafd66accb8108bc64c62edb0f322e3bcac50567761a67144812ca", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+++", + "pop_size": 25, + "var_lower_bounds": [ + -0.01331611324646731, + -0.6062619062265414, + -0.9890880753568537, + -0.818101050024029 + ], + "var_upper_bounds": [ + 1.340604643884352, + 1.1520470250444221, + 1.784058614467154, + 1.7439378206317415 + ], + "x_sha256": "0a13930c21ef2b7ae02ef6e38b896efc62b59a49eebe7fb9b9e419b829cd8df7" + }, + { + "constraint_directions": "<>", + "constraint_targets": [ + 0.607741847385851, + 0.4877645594977067 + ], + "f_sha256": "3d03ac0b6ee6325de60f74b06c819a01019317bd8f339c6a1437afe19c707856", + "fevals": 75, + "g_sha256": "97b2a7d7bdf621396c6d9998087d1d6d1e3e94c3ccd3918cc36840370f523d94", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+++", + "pop_size": 25, + "var_lower_bounds": [ + -0.01331611324646731, + -0.6062619062265414, + -0.9890880753568537, + -0.818101050024029 + ], + "var_upper_bounds": [ + 1.340604643884352, + 1.1520470250444221, + 1.784058614467154, + 1.7439378206317415 + ], + "x_sha256": "2fe7b02c23b60d22fbd9f6a1f0f13a4d21b28e362a58cff23ab9c75b89120e18" + }, + { + "constraint_directions": "<>", + "constraint_targets": [ + 0.607741847385851, + 0.4877645594977067 + ], + "f_sha256": "d2bd488ca7322083b2c96575440115c537388ea5a6ec0543b39d5b0c1f77630f", + "fevals": 100, + "g_sha256": "d848fa2f9da8c622a3ebcc1153f83510248deeb313eae197bcb7bd2fc22bb197", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+++", + "pop_size": 25, + "var_lower_bounds": [ + -0.01331611324646731, + -0.6062619062265414, + -0.9890880753568537, + -0.818101050024029 + ], + "var_upper_bounds": [ + 1.340604643884352, + 1.1520470250444221, + 1.784058614467154, + 1.7439378206317415 + ], + "x_sha256": "cb2ae15fa3453a2abd76a0ccad796d590d29aff7b38bd222b01c46d0752ba079" + } + ] + }, + { + "metadata": { + "author": "06YQD", + "date": "2024-04-27", + "description": "Randomly generated metadata", + "version": 1.8683436709075674 + }, + "problem": "TNK", + "reports": [ + { + "constraint_directions": "<<", + "constraint_targets": [ + 0.6771508915552242, + 0.12116145688982427 + ], + "f_sha256": "73c33b07b4370567b7eeaea24bb48613637d7b42ed3f49faafba14bbccd82c8c", + "fevals": 25, + "g_sha256": "231dac39841834d56c527e33db4e7058814c8720667e7c6ee15ae36e1cfa247f", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+++", + "pop_size": 25, + "var_lower_bounds": [ + -0.17943296290248112, + -0.9701078696904671, + -0.6268266868350327, + -0.25397189765420736 + ], + "var_upper_bounds": [ + 1.8997467379749975, + 1.3495577757673076, + 1.1547233189353576, + 1.9803220340371652 + ], + "x_sha256": "0776fd1a019e7ec4076ebd849e60871fb4de035d43605ddf698dd54f4e7cb50a" + }, + { + "constraint_directions": "<<", + "constraint_targets": [ + 0.6771508915552242, + 0.12116145688982427 + ], + "f_sha256": "11761959216f04859bd6025b44d475ee56f5e0162b645553068f9b3c9effa10d", + "fevals": 50, + "g_sha256": "65d89d3192c392d4080e35ac9f2926ff2c9c30546378d2f1d60550311157b3f6", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+++", + "pop_size": 25, + "var_lower_bounds": [ + -0.17943296290248112, + -0.9701078696904671, + -0.6268266868350327, + -0.25397189765420736 + ], + "var_upper_bounds": [ + 1.8997467379749975, + 1.3495577757673076, + 1.1547233189353576, + 1.9803220340371652 + ], + "x_sha256": "3626650e225175e2d4091065ec88524683da20ff67eb19fd84998ea8942931d2" + }, + { + "constraint_directions": "<<", + "constraint_targets": [ + 0.6771508915552242, + 0.12116145688982427 + ], + "f_sha256": "3e733579029c985a702dbfa5e75131b5b5ffc2400c314788526a86c77945197e", + "fevals": 75, + "g_sha256": "b59ff3007c59639b5ac7f78e6052ebf653ff26101de32ceebe5f8d4dfbc160aa", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+++", + "pop_size": 25, + "var_lower_bounds": [ + -0.17943296290248112, + -0.9701078696904671, + -0.6268266868350327, + -0.25397189765420736 + ], + "var_upper_bounds": [ + 1.8997467379749975, + 1.3495577757673076, + 1.1547233189353576, + 1.9803220340371652 + ], + "x_sha256": "7dcdec8bda5bceedaeeef36cad1b640e9561388816c6042ecf4115750f4206b1" + }, + { + "constraint_directions": "<<", + "constraint_targets": [ + 0.6771508915552242, + 0.12116145688982427 + ], + "f_sha256": "dc3ca3cdce31564a610fc2e4c046fe3d5700fd5ab04a296570031d71db3b843b", + "fevals": 100, + "g_sha256": "0a05f1539664d7add8fd6276dca68b26027554563d0fb48a2ac0b7df03bf5451", + "m": 3, + "n": 4, + "n_constraints": 2, + "names_f": [ + "f1", + "f2", + "f3" + ], + "names_g": [ + "g1", + "g2" + ], + "names_x": [ + "x1", + "x2", + "x3", + "x4" + ], + "obj_directions": "+++", + "pop_size": 25, + "var_lower_bounds": [ + -0.17943296290248112, + -0.9701078696904671, + -0.6268266868350327, + -0.25397189765420736 + ], + "var_upper_bounds": [ + 1.8997467379749975, + 1.3495577757673076, + 1.1547233189353576, + 1.9803220340371652 + ], + "x_sha256": "12ede9a8819bfd99ab2f50e67b13d3a7bcd888ae36768cbad2a580c839efab1f" + } + ] + } + ], + "software": "ParetoBench", + "software_version": "0.0.0" +} \ No newline at end of file diff --git a/tests/test_plotting.py b/tests/test_plotting.py index d8381cb..04d0922 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -231,6 +231,79 @@ def test_population_dvar_pairs_selection(): plt.close(fig) +def get_bound_lines(ax): + """ + Returns the x locations of the vertical bound lines drawn onto one of the histogram axes of a dvar pairs plot. + """ + return sorted(line.get_xdata()[0] for line in ax.get_lines() if line.get_linestyle() == "--") + + +def test_population_dvar_pairs_bounds(): + """ + The population's own bounds are used for the bound lines unless the caller overrides or disables them. + """ + pop = Population(x=np.random.random((5, 3)), f=np.random.random((5, 2))) + bounded = Population(x=pop.x, f=pop.f, var_lower_bounds=-np.ones(3), var_upper_bounds=2 * np.ones(3)) + + # The unbounded population draws nothing since its bounds are infinite + fig, axes = population_dvar_pairs(pop) + assert get_bound_lines(axes[0, 0]) == [] + plt.close(fig) + + # The bounded population draws its own bounds + fig, axes = population_dvar_pairs(bounded) + assert get_bound_lines(axes[0, 0]) == [-1.0, 2.0] + plt.close(fig) + + # Which can be turned off + fig, axes = population_dvar_pairs(bounded, plot_bounds=False) + assert get_bound_lines(axes[0, 0]) == [] + plt.close(fig) + + # User specified bounds take precedence over the population's + fig, axes = population_dvar_pairs(bounded, lower_bounds=np.zeros(3), upper_bounds=np.ones(3)) + assert get_bound_lines(axes[0, 0]) == [0.0, 1.0] + plt.close(fig) + + # A problem may also be used as the source of bounds + fig, axes = population_dvar_pairs(bounded, problem="ZDT1 (n=3)") + assert get_bound_lines(axes[0, 0]) == [0.0, 1.0] + plt.close(fig) + + # Only one source of bounds may be specified + with pytest.raises(ValueError, match="Only specify one of problem or the upper/lower bounds"): + population_dvar_pairs(bounded, problem="ZDT1 (n=3)", lower_bounds=np.zeros(3)) + + +def test_population_dvar_pairs_partial_bounds(): + """ + Bound lines are skipped for the individual variables which are unbounded. + """ + pop = Population( + x=np.random.random((5, 2)), + f=np.random.random((5, 2)), + var_lower_bounds=np.array([-1.0, -np.inf]), + var_upper_bounds=np.array([2.0, np.inf]), + ) + + fig, axes = population_dvar_pairs(pop) + assert get_bound_lines(axes[0, 0]) == [-1.0, 2.0] + assert get_bound_lines(axes[1, 1]) == [] + plt.close(fig) + + +def test_history_dvar_pairs_bounds(): + hist = History.from_random(3, 2, 3, 1, 10, generate_bounds=True) + + fig, axes = hist.plot_dvar_pairs() + assert get_bound_lines(axes[0, 0]) == [] + plt.close(fig) + + fig, axes = hist.plot_dvar_pairs(plot_bounds=True, lower_bounds=np.zeros(3), upper_bounds=np.ones(3)) + assert get_bound_lines(axes[0, 0]) == [0.0, 1.0] + plt.close(fig) + + def test_population_dvar_pairs_errors(): """Test error cases""" pop = Population.from_random(n_objectives=2, n_decision_vars=3, n_constraints=1, pop_size=5) diff --git a/tests/utils.py b/tests/utils.py index 779a43a..1e6da95 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -79,6 +79,8 @@ def experiment_to_manifest(exp): "obj_directions": report.obj_directions, "constraint_directions": report.constraint_directions, "constraint_targets": report.constraint_targets.tolist(), + "var_lower_bounds": report.var_lower_bounds.tolist(), + "var_upper_bounds": report.var_upper_bounds.tolist(), "x_sha256": array_digest(report.x), "f_sha256": array_digest(report.f), "g_sha256": array_digest(report.g),