# Objectives

In BoTorch, an *objective* is a module that allows for convenient transformation
of model outputs into a scalar function to be optimized.
Typical use cases for this are the scalarization of outputs for a multi-output
model (see e.g. ^{[1]}), or optimization subject to outcome constraints,
which can be achieved by weighting the objective by the probability of
feasibility ^{[2]}.

When using classical analytic formulations of acquisition functions, one needs to be careful that the transformation results in a posterior distribution of the transformed outputs that still satisfies the assumptions of the analytic formulation. For instance, to use standard Expected Improvement on a transformed output of a model, the transformation needs to be affine (because Gaussians are closed under affine transformations). When using MC-based acquisition functions, however, fewer assumptions are required, and one can apply general transformations to the model outputs with relative impunity so long gradients can be back-propagated through the transformation.

All BoTorch objectives are derived from
`MCAcquisitionObjective`

.
BoTorch implements several MC-based objectives, including
`LinearMCObjective`

for linear
combinations of model outputs, and
`ConstrainedMCObjective`

for
constrained objectives (using a sigmoid approximation for the constraints).

## Using custom objectives

### Utilizing GenericMCObjective

The `GenericMCObjective`

allows
simply using a generic callable to implement an ad-hoc objective. The callable
is expected to map a `sample_shape x batch_shape x q x o`

-dimensional tensor of
posterior samples and an (optional) `batch_shape x q x d`

-dimensional tensor of
inputs to a `sample_shape x batch_shape x q`

-dimensional tensor of sampled
objective values.

For instance, say you have a multi-output model with $o=2$ outputs, and you want to optimize a $obj(y) = 1 - \|y - y_0\|_2$, where $y_0 \in \mathbb{R}^2$. For this you would use the following custom objective (here we can ignore the inputs $X$ as the objective does not depend on it):

```
obj = lambda xi, X: 1 - torch.linalg.norm(xi - y_0, dim=-1)
mc_objective = GenericMCObjective(obj)
```

### Implementing a custom objective module

Instead of using `GenericMCObjective`

, you can also implement your own
`MCAcquisitionObjective`

modules to make them easier to re-use, or support
more complex logic. The only thing required to implement
is a `forward`

method that takes in a
`sample_shape x batch_shape x q x o`

-dimensional tensor of
posterior samples and maps it to a
`sample_shape x batch_shape x q`

-dimensional tensor of sampled objective values.

A custom objective module of the above example would be

```
class MyCustomObjective(MCAcquisitionObjective):
def forward(self, samples, X=None):
return 1 - torch.linalg.norm(samples - y_0, dim=-1)
```