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

Refactor ACQF Optimization #535

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft

Refactor ACQF Optimization #535

wants to merge 31 commits into from

Conversation

jduerholt
Copy link
Contributor

@LukasHebing @sleweke-bayer

I created a new branch to be able to play a bit with my ideas on how to refactor the acqf optimization, I try to add more and document better, but for the start I just made a lot of comments in the code, maybe we can start to have a discussion starting from here.

I try to add more over the course of the week.

@LukasHebing
Copy link
Contributor

LukasHebing commented Mar 6, 2025

I see, where this is going. I´m not sure, what the role of the BotorchStrategy will be:

  • BotorchOptimizer should contain all the optimizer-gateways, and mapping to domain etc.
  • Some of the functions, e.g. finding the bounds and multiclass labels (or fixed features), get_infeasible_cost, or calc_acquisition, should go to the abstract optimizer class AcquisitionOptimizer

Then there would be nothing remaining in this class.

Can we use the PredictiveStrategy base class as the default strategy, which accepts an optimizer data-model or mapped instance?

@jduerholt
Copy link
Contributor Author

Hmm, I would like to keep the BotorchStrategy, and I also think that is needed, as the following methods are highly botorch specifc but not related to the acqf optimizer

  • _fit
  • _predict
  • Setup of the acquistion functions etc.

You are right that the new optimizer class would need access to calc_acquisition, but I would also like to keep it in BotorchStrategy, because it belongs there and not the optimizer and is more convenient to the user to just perform strategy.calc_acquisition on a dataframe of interest.

What do you think about the following idea?

We move every methodthat is really optimizer specific to the acqf_optimizer class but upon init or optimize call we also give it the reference to the strategy object, doing it like this would give the optimizer access to all methods in the strategy object. This would give us an easy path forward without the need to rewrite too much.

@LukasHebing
Copy link
Contributor

That´s a good approach,
I can go ahead and try to move methods around today and tomorrow.

@jduerholt
Copy link
Contributor Author

jduerholt commented Mar 7, 2025

I can go ahead and try to move methods around today and tomorrow.

@LukasHebing Perfect, I now blocked also time for Monday on my calendar to work on this to have it in asap, feel free to push so that I can than maybe work on the same PR.

@LukasHebing
Copy link
Contributor

@jduerholt: In general, I would need a few days more, than you can check out.

About the Bounds:
However, I am not sure, how to pass the information on the bounds to the optimizer. In my understanding, the GLOBAL bounds are defined in the domain, which is passed to the optimize function. There, we have the utility function "get_bounds".
I don`t see, why we would use other bounds for this high-level optimizer call. In the BotorchOptimizer class, the optimize function is only called once. All repeated optimizations on sub-spaces etc. are then handled in this function.
Other optimizers (e.g. the GA) would handle this differently

@LukasHebing
Copy link
Contributor

LukasHebing commented Mar 10, 2025

@jduerholt: I sorted the methods, as I thought would be useful. The optimize function would need from the strategy:

  • domain
  • input_preprocessing_specs
  • experiments (optional)

It would return candidates as Tensor, and the conversion to pd.DataFrame happens in the strategy class.

However, I really don't know what's going on here (end of the BotorchOptimizer´s optimize function):

sp = ShortestPathStrategy(
    data_model=ShortestPathStrategyDataModel(
        domain=self.domain,
        start=self.experiments.iloc[-1].to_dict(),
        end=self._postprocess_candidates(candidates).iloc[-1].to_dict(),
    ),
)

step = pd.DataFrame(sp.step(sp.start)).T
return pd.concat((step, self.predict(step)), axis=1)

@jduerholt
Copy link
Contributor Author

Thx, I will have a look tmr. I got sick so I could not do anything today. I hope it is better tmr.

@LukasHebing
Copy link
Contributor

@jduerholt
I moved the optimization of the "all-categoric" case to the abstract AcquisitionOptimizer base class. I am wondering, why this only checks for categoric, and discrete input types, and not for continuous inputs with a fixed stepsize:

len(domain.inputs.get(includes=[DiscreteInput, CategoricalInput]),
            ) == len(domain.inputs)

How are the conto-inputs with stepsize calculated? As continuous and then later mapped on to the grid?

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.

2 participants