Skip to content

Multi-fidelity surrogate models#721

Open
jpenn2023 wants to merge 8 commits intodev/mfbofrom
dev-mfbo-main-surrogate-models
Open

Multi-fidelity surrogate models#721
jpenn2023 wants to merge 8 commits intodev/mfbofrom
dev-mfbo-main-surrogate-models

Conversation

@jpenn2023
Copy link
Collaborator

Adding multi-fidelity properties for the SearchSpace class and multi-fidelity Gaussian process classes with the required fidelity kernels.

Comment on lines +37 to +38
# TODO Jordan MHS: _ModelContext is used by fidelity surrogate models now so may deserve
# its own file.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I move model context to its own file since it is used in multiple Gaussian process files?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not important but if wanted feel free to put it into a gaussian_process/utils.py or similar

# its own file.
@define
class _ModelContext:
"""Model context for :class:`GaussianProcessSurrogate`."""
Copy link
Collaborator Author

@jpenn2023 jpenn2023 Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And how would this comment be best amended?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by adding the other class OR getting rid of the explicit link and using a generic not-linked word which means it never has to be updated again ever in the history of the universe

@property
def n_fidelity_dimensions(self) -> int:
"""The number of fidelity dimensions."""
# Possible TODO: Generalize to multiple fidelity dimensions
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This todo is here to spark discussion - will remove after review.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I would propose to keep everything focused around a single dimension, unless it is very clear and easy how to go on.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in analogy to the task, the fidelity is kept at max 1 fidelity parameter and everything els eis out of scope for the MF branch

# See base class.

kernel_factory: KernelFactory = field(init=False, default=None)
"""Design kernel is set to Matern within SingleTaskMultiFidelityGP."""
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opinions on referencing other packages' classes on in docstrings?

@AdrianSosic AdrianSosic force-pushed the dev-mfbo-main-surrogate-models branch from ccac1f8 to 178772a Compare February 4, 2026 16:58
batch_shape=batch_shape,
)

fidelity_covar_module = self.fidelity_kernel_factory(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Index kernels used as categorical fidelity kernels do not need train_x and train_y to set any lengthscales but the KernelFactory class requires these as arguments in __call__. How should we handle this difference? E.g., we could (a) redundantly pass train_x and train_y into the fidelity kernels' __call__ or (b) make train_x and train_y optional in the parent KernelFactory class. The latter choice seems correct to me but wanted to check before changing existing code.

Copy link
Collaborator

@AVHopp AVHopp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments on everything but the multi_fidelity.py file, as I guess that we will discuss this today in a bit more detail.

converter=optional_c(int),
validator=optional_v([instance_of(int), ge(1)]),
)
"""Matrix rank controlling the degree of correlation between outputs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short half-sentence about what happens when it is None

class SearchSpaceTaskType(Enum):
"""Enum class for different types of task and/or fidelity subspaces."""

SINGLETASK = "SINGLETASK"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would prefer NOTASK over SINGLETASK as the latter one could also be read as "has a single task parameter"

def fidelity_idx(self) -> int | None:
"""The column index of the task parameter in computational representation."""
try:
# See TODO [16932] and TODO [11611]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be stupid, but what do [16932] and [11611] refer to?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably to the generalization to more than 1 task parameter, numbers refer to old Azure items

imo remove

try:
# See TODO [16932]
fidelity_param = next(
p for p in self.parameters if isinstance(p, (TaskParameter,))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why (TaskParameter,), shouldn't TaskParameter also do the job?

return 1 if fidelity_param is not None else 0

@property
def n_fidelity_dimensions(self) -> int:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, this does not calculate the number of fidelity dimensions but simply returns 1 if there is at least one fidelity dimension. This thus implicitly assumes that there can only be one such dimension. Is this guaranteed at some other place?
Even if, I would propose that this still performs counting in the same way as it is done in n_task_dimensions and not hard-code the 1 as a return value

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: It seems like this is only done when accessing task_type, right? So a user could still create a search space with multiple such parameters, right? Or has this been addressed in the previous PR already?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is jsut a copycat of n_task_dimensions so you can assume the same assumptions etc

) -> Kernel:
effective_dims = train_x.shape[-1] - len(
[p for p in searchspace.parameters if isinstance(p, TaskParameter)]
[
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have the properties for exactly calculating this?

)


DefaultFidelityKernelFactory = IndexFidelityKernelFactory
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this assignment here? For easier import in other files? SHouldn't a default de assigned there?

@property
def n_fidelity_dimensions(self) -> int:
"""The number of fidelity dimensions."""
# Possible TODO: Generalize to multiple fidelity dimensions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I would propose to keep everything focused around a single dimension, unless it is very clear and easy how to go on.


@property
def is_multi_fidelity(self) -> bool:
"""Are there any fidelity dimensions?"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how a docstring should look like, please reformulate properly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there an is_multi_task property? if not please add for consistency

def _make_parameter_scaler_factory(
parameter: Parameter,
) -> type[InputTransform] | None:
# For GPs, we let botorch handle the scaling. See [Scaling Workaround] above.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "above" is not in this file, so please refer to the correct part of the code base

@jpenn2023 jpenn2023 force-pushed the dev-mfbo-main-surrogate-models branch from 2e64082 to 8e8547b Compare February 27, 2026 07:29
@AdrianSosic
Copy link
Collaborator

Hey @jpenn2023, I just wanted to have a look at your PR but then noticed that something went wrong with your rebase. I know you asked me about the weird diff shown on Github (and yes, that is generally still a problem), but it turns out that your diff is wrong for a different reason – namely because you somehow rebased your commits together with an entire bunch of other commits 😬 Can you quickly fix it and ping me once ready for review?

@jpenn2023 jpenn2023 force-pushed the dev-mfbo-main-surrogate-models branch from d96cf75 to 2c513b0 Compare March 6, 2026 12:25
@AdrianSosic AdrianSosic force-pushed the dev-mfbo-main-surrogate-models branch from 2c513b0 to 754d5da Compare March 6, 2026 13:48
@AdrianSosic AdrianSosic changed the base branch from dev/mfbo to main March 6, 2026 13:54
@AdrianSosic AdrianSosic changed the base branch from main to dev/mfbo March 6, 2026 13:55
@AdrianSosic AdrianSosic force-pushed the dev-mfbo-main-surrogate-models branch from 754d5da to 563b86a Compare March 6, 2026 17:21
@AdrianSosic AdrianSosic changed the base branch from dev/mfbo to main March 6, 2026 17:22
@AdrianSosic AdrianSosic changed the base branch from main to dev/mfbo March 6, 2026 17:22
@AdrianSosic AdrianSosic force-pushed the dev-mfbo-main-surrogate-models branch from 563b86a to 88db97f Compare March 6, 2026 17:26
@AdrianSosic
Copy link
Collaborator

Hey @jpenn2023, have a look, this should now give you a clean picture of your PR content, right?

@jpenn2023
Copy link
Collaborator Author

Hey @jpenn2023, have a look, this should now give you a clean picture of your PR content, right?

Hi @AdrianSosic. Yes, this view looks right to me.

Copy link
Collaborator

@Scienfitz Scienfitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my biggest question is why the concepts of TL and MF are now deedply intertwined by using a single searchspace property for combining them isntead of two properties characterizing the searchspaces task / fidelity character

"""Flag for hybrid search spaces resp. compatibility with hybrid search spaces."""


class SearchSpaceTaskType(Enum):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i really dont get why TL and MF thigns are mixed in here to havea single property, imo that dosnt amke sense

I would expect to have a SearchSpaceTaskType (with SINGLETASK and CATEGORICALTASK) and SearchSpaceFidelityType (with SINGLEFIDELITY, CATEGORICALFIDELITY and NUMERICALFIDELITY etc) which their respective values and not a mixed kind of thing

is there any reason younare enforcing such a mxiture between unrelated concepts?


@property
def fidelity_idx(self) -> int | None:
"""The column index of the task parameter in computational representation."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""The column index of the task parameter in computational representation."""
"""The column index of the fidelity parameter in computational representation."""

def fidelity_idx(self) -> int | None:
"""The column index of the task parameter in computational representation."""
try:
# See TODO [16932] and TODO [11611]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably to the generalization to more than 1 task parameter, numbers refer to old Azure items

imo remove


@property
def n_fidelities(self) -> int:
"""The number of tasks encoded in the search space."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""The number of tasks encoded in the search space."""
"""The number of fidelities encoded in the search space."""

return 1 if fidelity_param is not None else 0

@property
def n_fidelity_dimensions(self) -> int:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is jsut a copycat of n_task_dimensions so you can assume the same assumptions etc

@property
def n_fidelity_dimensions(self) -> int:
"""The number of fidelity dimensions."""
# Possible TODO: Generalize to multiple fidelity dimensions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in analogy to the task, the fidelity is kept at max 1 fidelity parameter and everything els eis out of scope for the MF branch


@property
def is_multi_fidelity(self) -> bool:
"""Are there any fidelity dimensions?"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there an is_multi_task property? if not please add for consistency

"""Class variable encoding whether or not the surrogate supports transfer
learning."""

supports_multi_fidelity: ClassVar[bool]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is an inconsistent treatment of the supports_* proeprties now ehere _multi_output is set in the abse class but _transfer_learning and _fidelity are not set

THe altter requries many explicit lines in the derived classes which could eba voided if we explicitly set it to False here and require overwriting if its actually compatible

I would prefer that but remember we had disucssion on it some time ago.

In order to unify this, can you man a note here so this is revisited / rediscussed? for this PR its fine I guess

# problem since the resulting `posterior` method of that object is exposed
# to `optimize_acqf_*`, which is configured to be called on the original scale.
# Moving the scaling operation into the botorch GP object avoids this conflict.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this class not have the new supports_multi_fidelity set?

# CategoricalFidelityParameter or NumericalDiscreteFidelityParameter.
# This can be achieved without the user having to specify the surroagte model,
# e.g., by
# * using a dispatcher factory which decides surrogate model on fit time
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the respective class is implemented, can you point out where the dispatching is done? or is this for another PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants