Container Objects¶
Some objects are included in ParetoBench to enable users to manipulate and save data related to multi-objective optimizations. We use this page to explain some of their usage.
import numpy as np
import os
import paretobench as pb
import tempfile
Population Objects¶
The basic building block of optimization data is a Population which is supposed to represent the population of individuals in a single generation of a genetic algorithm. The populations contain the decision variables and the values of the objectives and constraints. All of these are 2D numpy arrays with the first dimension being the batch dimension and having a length equal to the population size. This is true even in the case of not having any constraints (for instance) where the second dimension is set to length zero. It also contains a place to hold the number of function evaluations performed up until this point for use in analyzing performance.
# Create an example population
pop = pb.Population(
x=np.random.random((32, 10)), # 32 individuals worth of 10 decision vars
f=np.random.random((32, 2)), # 2 objectives
# Not specifying an array (constraints here) will cause it to populate empty based on other's shape
fevals=32,
)
print(pop)
# Examine some of the parameters
print(f"Decision variables: {pop.x.shape}")
print(f"Objectives: {pop.f.shape}")
print(f"Constraints: {pop.g.shape}")
print(f"Function evaluations: {pop.fevals}")
Population(size=32, vars=10, objs=[--], cons=[], fevals=32) Decision variables: (32, 10) Objectives: (32, 2) Constraints: (32, 0) Function evaluations: 32
Population objects can also store names associated with the decision variables, objectives, and constraints. This is useful for practical optimization problems.
# Create a population with some names
pop = pb.Population(
x=np.random.random((32, 10)),
f=np.random.random((32, 2)),
g=np.random.random((32, 1)), # Add a single constraint this time
names_x=[f"Decision var {idx}" for idx in range(pop.x.shape[1])],
names_f=["Objective_1", "Objective_2"],
names_g=["Important_Constraint"],
)
pop
Population(size=32, vars=10, objs=[--], cons=[<0.0e+00], fevals=32)
Populations default to representing minimization problems with constraints that are satisfied when each g >= 0. This can be modified as in the following code block. Configuring these settings is important when using container methods to find feasible individuals and in calculation of domination.
# Create a population and specify the objective / constraint directions and target
pop = pb.Population(
x=np.random.random((32, 10)),
f=np.random.random((32, 3)),
g=np.random.random((32, 2)),
obj_directions="+-+", # "+" indicates maximization, "-" inidicates minimization
constraint_directions="><", # "<" means satisifed when g<target and ">" means satsified when g>target
constraint_targets=np.array([0.33, 0.66]), # >0.33 constraint and <0.66 constraint
)
pop
Population(size=32, vars=10, objs=[+-+], cons=[>3.3e-01,<6.6e-01], fevals=32)
History Objects¶
Multiple populations are combined together into a History object that represents reports of the population of an optimization algorithm in the course of solving a problem. Each report will typically be a single generation within the genetic algorithm. The history object also holds a location for the name of the problem which is being solved.
# Create some population objects which will go into the history. We will use the random generation helper function here.
n_reports = 10
reports = [
pb.Population.from_random(n_objectives=2, n_decision_vars=30, n_constraints=0, pop_size=50)
for _ in range(n_reports)
]
# Construct the history object
hist = pb.History(
reports=reports,
problem="WFG1", # Use ParetoBench single line format for maximum compatibility with plotting, etc
)
print(hist)
# Print some of the properties
print(f"Problem: {hist.problem}")
print(f"Number of reports: {len(hist.reports)}")
History(problem='WFG1', reports=10, vars=30, objs=[--], cons=[]) Problem: WFG1 Number of reports: 10
Experiment Objects¶
Finally, everything comes together in the Experiment object which represents a user performing several optimizations on a benchmark problem for one algorithm and one set of hyperparameters for that algorithm. These objects are saveable in an HDF5 file and also contain useful metadata for describing the experiment and how it was made.
# Create some random history objects to store
runs = []
for _ in range(32):
runs.append(
pb.History.from_random(
n_populations=15,
n_objectives=2,
n_decision_vars=10,
n_constraints=0,
pop_size=32,
)
)
# Create an example experiment
exp = pb.Experiment(
runs=runs, # The history objects in this experiment
name="NSGA-II (default params)", # A name for this experiment (for example, the algorithm and parameters used)
author="The author of ParetoBench", # Who created this
software="ParetoBench example notebook", # What software created this object
software_version="1.0.0",
comment="An example of an experiment object",
)
# Let's save the object to disk. We will use a temp. directory to not pollute your computer :)
with tempfile.TemporaryDirectory() as dir:
# The filename we'll use
fname = os.path.join(dir, "my_experiment.h5")
# Save to disk
exp.save(fname)
# Load it back in
exp_loaded = pb.Experiment.load(fname)
# Check out the loaded object
print(exp_loaded)
print(f"Number of histories: {len(exp_loaded.runs)}")
print(f"Name: {exp_loaded.name}")
print(f"Creation time: {exp_loaded.creation_time}") # A UTC timestamp is added on object creation
Experiment(name='NSGA-II (default params)', created='2026-07-10', author='The author of ParetoBench', software='ParetoBench example notebook 1.0.0', runs=32) Number of histories: 32 Name: NSGA-II (default params) Creation time: 2026-07-10 17:22:55.833286+00:00