Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve DOFs and Objectives for better control of Pareto optimization #66

Merged
merged 8 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/source/dofs.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Degrees of freedom (DOFs)
+++++++++++++++++++++++++

Continuous degrees of freedom
-----------------------------

A degree of freedom is a variable that affects our optimization objective. We can define a simple DOF as

.. code-block:: python
Expand Down
80 changes: 72 additions & 8 deletions docs/source/objectives.rst
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
Objectives
++++++++++

We can describe an optimization problem as a list of objectives to. A simple objective is
Objectives are what control how optimal the output of our experiment is, and are defined by ``Objective`` objects.

``blop`` combines one or many ``Objective`` objects into an ``ObjectiveList``, which encapsulates how we model and optimize our outputs.

Fitness
-------

A fitness objective is an ``Objective`` that minimizes or maximizes a given value.

* Maximize the flux of a beam of light.
* Minimize the size of a beam.

We can construct an objective to maximize some output with

.. code-block:: python

from blop import Objective

objective = Objective(name="y1", target="max")
objective = Objective(name="some_output", target="max") # or "min"

Given some data, the ``Objective`` object will try to model the quantity "y1" and find the corresponding inputs that maximize it.
The objective will expect that this quantity will be spit out by the experimentation loop, so we will check later that it is set up correctly.
There are many ways to specify an objective's behavior, which is done by changing the objective's target:
Given some data, the ``Objective`` object will try to model the quantity ``y1`` and find the corresponding inputs that maximize it.
thomaswmorris marked this conversation as resolved.
Show resolved Hide resolved
We can also apply a transform to the value to make it more Gaussian when we fit to it.
This is especially useful when the quantity tends to be non-Gaussian, like with a beam flux.

.. code-block:: python

from blop import Objective

objective = Objective(name="y1", target="min") # minimize the quantity "y1"
objective = Objective(name="some_output", target="max", transform="log")

objective = Objective(name="some_output", target="max", transform="arctanh")
thomaswmorris marked this conversation as resolved.
Show resolved Hide resolved


objective = Objective(name="y1", target=2) # get the quantity "y1" as close to 2 as possible
.. code-block:: python

objective = Objective(name="y1", target=(1, 3)) # find any input that puts "y1" between 1 and 3.

Expand All @@ -31,4 +46,53 @@ In this case, a smart thing to do is to set ``log=True``, which will model and s

from blop import Objective

objective = Objective(name="some_strictly_positive_quantity", target="max", log=True)
objective = Objective(name="some_strictly_positive_output", target="max", log=True)


Constraints
-----------

A constraint objective doesn't try to minimize or maximize a value, and instead just tries to maximize the chance that the objective is within some acceptable range.
This is useful in optimization problems like

* Require a beam to be within some box.
* Require the wavelength of light to be a certain color.
* We want a beam to be focused enough to perform some experiment, but not necessarily optimal.

.. code-block:: python

# ensure the color is approximately green
objective = Objective(name="peak_wavelength", target=(520, 530), units="nm")

# ensure the beam is smaller than 10 microns
objective = Objective(name="beam_width", target=(-np.inf, 10), units="um", transform="log")

# ensure our flux is at least some value
objective = Objective(name="beam_flux", target=(1.0, np.inf), transform="log")
thomaswmorris marked this conversation as resolved.
Show resolved Hide resolved



Validity
--------

A common problem in beamline optimization is in the random or systematically invalid measurement of objectives. This arises in different ways, like when

* The beam misses the detector, leading our beam parser to return some absurdly small or large value.
* Some part of the experiment glitches, leading to an uncharacteristic data point.
* Some part of the data postprocessing pipeline fails, giving no value for the output.

We obviously want to exclude these points from our model fitting, but if we stopped there, inputs that always lead to invalid outputs will lead to an infinite loop of trying to sample an interesting but invalid points (as the points get immediately removed every time).
thomaswmorris marked this conversation as resolved.
Show resolved Hide resolved
The set of points that border valid and invalid data points are often highly nonlinear and unknown *a priori*.
We solve this by implementing a validity model for each ``Objective``, which constructs and fits a probabilistic model for validity for all inputs.
Using this model, we constrain acquisition functions to take into account the possibility that the output value is invalid, meaning it will eventually learn to ignore infeasible points.

We can control the exclusion of data points in two ways. The first is to specify a ``trust_domain`` for the objective, so that the model only "trusts" points in that domain. For example:

.. code-block:: python

# any beam smaller than two pixels shouldn't be trusted.
# any beam larger than 100 pixels will mess up our model and aren't interesting anyway
objective = Objective(name="beam_size", trust_domain=(2, 100), units="pixels")

This will set any value outside of the ``trust_domain`` to ``NaN``, which the model will learn to avoid.
The second way is to ensure that any invalid values are converted to ``NaN`` in the diagnostics, before the agent ever sees them.
Loading
Loading