Skip to content

Conversation

AVHopp
Copy link
Collaborator

@AVHopp AVHopp commented Aug 19, 2025

This PR adds continuous interpoint constraints, and is the re-visited version of #345.

It is for now in draft state as I am currently re-visiting the old version of this branch and investigate what we can use.

I am also explicitly tagging @StefanPSchmid here as he was interested in this feature and planned to potentially implement a variant for discrete spaces.

Fork for example and userguide:
https://avhopp.github.io/baybe_dev/latest/examples/Constraints_Continuous/interpoint.html and https://avhopp.github.io/baybe_dev/latest/userguide/constraints.html#continuous-constraints

@AVHopp AVHopp self-assigned this Aug 19, 2025
@AVHopp

This comment was marked as outdated.

@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch from de882d4 to 6a2086b Compare August 25, 2025 07:54
@AVHopp AVHopp requested review from Copilot and removed request for Copilot August 25, 2025 07:54
@AVHopp AVHopp marked this pull request as ready for review August 25, 2025 08:24
Copilot

This comment was marked as outdated.

@Scienfitz Scienfitz requested a review from Copilot August 29, 2025 10:16
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for continuous interpoint constraints to BayBE, enabling constraints that apply across all points in a batch rather than to individual points. An interpoint constraint like x_1 + x_2 <= 1 enforces that the sum of all x_1 values plus the sum of all x_2 values in the entire batch must not exceed 1.

  • Added is_interpoint flag to ContinuousLinearConstraint with corresponding logic changes
  • Implemented interpoint constraint handling in sampling and optimization methods
  • Added comprehensive test coverage for various interpoint constraint scenarios

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
baybe/constraints/continuous.py Added is_interpoint field and updated to_botorch method to handle interpoint constraint index calculation
baybe/searchspace/continuous.py Added interpoint constraint detection properties and updated sampling logic to handle batch-aware constraint enforcement
baybe/recommenders/pure/bayesian/botorch.py Updated recommender to disable sequential optimization and pass batch size for interpoint constraints
tests/constraints/test_constraints_continuous.py Added comprehensive test cases for interpoint equality/inequality constraints with tolerance validation
tests/conftest.py Added test constraint definitions for interpoint scenarios
tests/hypothesis_strategies/constraints.py Updated constraint generation strategy to include interpoint flag
examples/Constraints_Continuous/interpoint.py Added example demonstrating interpoint constraint usage
docs/userguide/constraints.md Added documentation explaining interpoint constraints with limitations
CHANGELOG.md Added feature entry

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

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.

Prädikat almost gucci

@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch from 92fa396 to c616413 Compare August 29, 2025 13:27
@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch from efdbde0 to fe7b512 Compare September 8, 2025 06:51
Copy link
Collaborator

@AdrianSosic AdrianSosic left a comment

Choose a reason for hiding this comment

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

Hi @AVHopp. Here the first batch of comments. Haven't gone through everything yet, so more will follow 🙃

Comment on lines 61 to 63
interpoint constraints do so **across** the points of the batch. That is, an
interpoint constraint of the form ``x + y <= 1`` technically encodes
``sum(x_i) + sum(y_i) <= 1`` for all points ``i`` in the batch.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know what you are trying to say here, but the it's written is really confusing. Because what you write is it encodes "some inequality" and this encoding is done for all points i --> it sort of suggests that there are as many inequalities as there are points i.

I'd rather rephrase it to something like "encodes sum_i(x_i) + sum_i(y_i) <= 1, where x and y are ..." or similar. You know what I mean?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not entirely sure - but I will try to see if I can simply LaTeX-ify this which should make clear what exactly is meant.

Copy link
Collaborator

Choose a reason for hiding this comment

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

to clarify: if I take your statement word by word and "expand it", I'd get

  • sum(x_1) + sum(y_1) <= 1
  • sum(x_2) + sum(y_2) <= 1
  • ...

Since you write that this constraint holds for all i

Copy link
Collaborator

Choose a reason for hiding this comment

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

And since it has never really defined what x_i really is, it could be the case that a reasonable sum operation exists for x_i itself (since the sum also has no index)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have changed the docstring to a rather minimal example. I guess that people that are interested in this feature would rather have a look at examples/user guide anyway, so I would try to keep the docstring here rather short. Please resolve if you agree or propose a very specific alternative formulation otherwise.


from baybe.utils.torch import DTypeFloatTorch

# NOTE: The interpoint constraint case requires indices to be a 2-d tensor.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I have some problems with this comment:

  • It points to a page whose content is going to change (i.e. pointing to latest)
  • The docstring of optimize_acqf is super long, so it's not immediately clear where to look

--> better create a permalink to the exact line in the botorch github repo and point to that

  • Finally, when you say "adjustements", it sounds like the non-interpoint case is the default and in order to turn into interpoint indices, you slightly modify the tensor. But these things are really just completely different in nature, which is also reflected by the fact that your code uses two completely independent construction paths

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have moved and changed the comment in 27ee1c9 is it better now?


for c in self.constraints_lin_eq + self.constraints_lin_ineq:
if not c.is_interpoint:
param_indices, coefficients, rhs = c.to_botorch(self.parameters)
Copy link
Collaborator

Choose a reason for hiding this comment

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

why has the logic of the intrapoint case changed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Huh, that might be a left-over. Will investigate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ahh, I see: This is relevant for the case of having both inter- and intrapoint constraints: If we do not have any interpoint constraints, we can (and actually will) simply use the old logic, see the if part. If we have both, we however need to manipulate the non-interpoint constraints by copying it for each "row" that we will have in the interpoint constraint. Does this make sense?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is that a requirement from botorch? I'm just surprised that they then also expect a different format for the "regular" constraints, just because we additionally pass batch constraints. Have you checked that this is really needed? What happens if you pass the intrapoint constraints as 1d versions together with interpoint constraints in 2d?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note that we are in the place of the polytope sampling here. This requires us to transform/flatten the space in order to be able to sample using interpoint constraints - see here: meta-pytorch/botorch#2468
That is, you cannot hand over interpoint constraints in 2D notation as they are not guaranteed to work - see the discussion resp. the following part of it:
image

And consequently, as we literally have to reshape the whole space, we also need to reshape the intrapoint (in-)equalities by adjusting their indices.

equality_constraints=eq_constraints,
inequality_constraints=ineq_constraints,
)
points = points.reshape(batch_size, points.shape[-1] // batch_size)
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is this a // here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because torch explicitly requires int when reshaping:
image

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, I get this part, but then your approach is suboptimal. Because by using //, you are silently dropping potential error cases that could happen if the result of your computation is non-integer!
Just imagine that points.shape[-1] is 5 but batch_size is 2 --> silent bug.

And if you really didn't care about having an explicit specification of the remainder dimension, you could have just used -1, which auto-infers the size, which would have been much simpler. So your version is neither simple, nor logic-safe.

So I'd recommend to use a strict conversion to int and then pass that instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Changed this in 0824606 to make it more robust, please resolve if happy :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

see new thread

@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch 2 times, most recently from dfc2449 to e79d582 Compare September 10, 2025 14:11

if batch_size is None and self.is_interpoint:
raise RuntimeError(
"No ``batch_size`` set but using interpoint constraints."
Copy link
Collaborator

Choose a reason for hiding this comment

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

missing white space at the end --> incorrectly rendered
But I think you can actually just remove the second line – it's an obvious statement (you could add this to "any" error message in our codebase) since error are "always" unintended.

(there are only a few exceptions when such a "useless" error message makes sense, namely when the error is needed to block a certain execution path for type safety that would otherwise be left open, but this is not the case here)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will remove it. My intention was that this error is something that a user should never be able to see at all - so if this error shows up, it demonstrates that something has gone wrong * internally* - in contrast to stuff like "User is stupid because of miosconfiguration"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in cae7650

Copy link
Collaborator Author

@AVHopp AVHopp Sep 12, 2025

Choose a reason for hiding this comment

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

Based on your other comment, I realized that those should then rather be asserts. Will change here, please resolve this comment if you agree with "yes, those should be asserts" - see 84ccdb0

torch.tensor(param_indices),
torch.tensor(
[self._multiplier * c for c in self.coefficients], dtype=DTypeFloatTorch
[self._multiplier * c for c in coefficients], dtype=DTypeFloatTorch
Copy link
Collaborator

Choose a reason for hiding this comment

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

you're doing unnecessary conversions between lists and tensor here + unnecessary list comprehensions. simply turn the type of coefficients to Tensor at the very top and keep everything in torch. make everything shorter, easier to read, and more efficient

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Changed in 58d0a42 - thought about maybe even just having something in the style of coefficients = torch.Tensor(...) is not self:is_interpoint else torch.Tensor(...).repeat ..... but I had the feeling that this actually made readability worse. Let me know what you think :)

@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch 2 times, most recently from b819abb to 74d546e Compare September 12, 2025 11:44
@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch from d051cad to 23cf823 Compare September 23, 2025 08:13
@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch from 23cf823 to 0e8682e Compare September 23, 2025 08:49
@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch from 3922e6a to 2c5dc47 Compare September 23, 2025 09:47
@AVHopp
Copy link
Collaborator Author

AVHopp commented Sep 23, 2025

2. **Interpoint constraints:** These constraints model the relationship of different experiments
across the whole batch (*inter-*) of exeriments (*-points*).

#### Intrapoint constraints
Copy link
Collaborator

Choose a reason for hiding this comment

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

another suggestion you might consider: I personally find it a bit hard to read Itrapoint different form Interpoint. So Why not sue hyphens (or dreadful emdashes if thats correct in this case):

Inter-Point Constraints
Intra-Point Constraints

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Idea was to have it consistent with how it is called in the code. I am however also open to have it different in the texts, but would summon @AdrianSosic for his opinion.

interpoint constraints models constraints **across** the points of the batch.
That is, an interpoint constraint of the form $x + y <= 1$ encodes
$$
\sum_{i\text{ in batch}}x_i + \sum_{i\text{ in batch}}y_i <= 1
Copy link
Collaborator

Choose a reason for hiding this comment

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

doesnt proplery render:
image

You might also change the index to b to distinguish it from the i used for itra-point constraints

Maybe make this a "large" equation instead of an inline one? This would also make the "in batch" appear below the sigma and thus look nicer

results_df = pd.DataFrame(results_log)

# fmt: off
fig, axs = plt.subplots(1, 2, figsize=(10, 4));
Copy link
Collaborator

Choose a reason for hiding this comment

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

would there be any merit to add a second row of results showing how it would look like unconstrained? Ie we would nicely see the violations off of the required and limit lines

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will play around with that and see

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thought about this, and I do not think that we should add this. Reasons:

  • The example is about demonstrating that the feature works, not that you will not have this behavior if you do not use the feature
  • It would add quite some boilerplate code
  • If we would want to add a more detailed comparison, I would prefer to then re-introduce a true chemical model as we could then say "Look, without the constraint it just uses a lot of solvent/wastes catalyst" (or similar) which cannot be guaranteed if we use random stuff

label="Total",
)
plt.axhline(y=60, color="red", linestyle="--", label="Required")
plt.title("Solvent: Individual + Total")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Individual + Total is kind of obsolete and already part of the caption, perhaps better Solvent (Constrained)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 43e3257 new version is already up in the fork, please resolve if happy.

Copy link
Collaborator

Choose a reason for hiding this comment

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

last minor plot things

  • make the red dashed line not write over existing lines, ie put it at zorder=0
  • make the y axis start at 0, otherwise its very deceiving and it looks like the Exp lines do not add up to the Budget

@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch from a570f35 to 03280fb Compare September 26, 2025 08:35
@AVHopp AVHopp force-pushed the feature/continuous_interpoint_constraints branch from 43e3257 to 1e7ff6d Compare September 26, 2025 10:03
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.

3 participants