-
Notifications
You must be signed in to change notification settings - Fork 57
Add continuous interpoint constraints #625
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
base: main
Are you sure you want to change the base?
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
de882d4
to
6a2086b
Compare
There was a problem hiding this 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 toContinuousLinearConstraint
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.
There was a problem hiding this 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
92fa396
to
c616413
Compare
efdbde0
to
fe7b512
Compare
There was a problem hiding this 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 🙃
baybe/constraints/continuous.py
Outdated
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
baybe/constraints/continuous.py
Outdated
|
||
from baybe.utils.torch import DTypeFloatTorch | ||
|
||
# NOTE: The interpoint constraint case requires indices to be a 2-d tensor. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
And consequently, as we literally have to reshape the whole space, we also need to reshape the intrapoint (in-)equalities by adjusting their indices.
baybe/searchspace/continuous.py
Outdated
equality_constraints=eq_constraints, | ||
inequality_constraints=ineq_constraints, | ||
) | ||
points = points.reshape(batch_size, points.shape[-1] // batch_size) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see new thread
dfc2449
to
e79d582
Compare
baybe/constraints/continuous.py
Outdated
|
||
if batch_size is None and self.is_interpoint: | ||
raise RuntimeError( | ||
"No ``batch_size`` set but using interpoint constraints." |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in cae7650
There was a problem hiding this comment.
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
baybe/constraints/continuous.py
Outdated
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 :)
b819abb
to
74d546e
Compare
Co-authored-by: AdrianSosic <[email protected]>
Co-authored-by: AdrianSosic <[email protected]>
This reverts commit f458310.
d051cad
to
23cf823
Compare
23cf823
to
0e8682e
Compare
3922e6a
to
2c5dc47
Compare
@AdrianSosic @Scienfitz please see here for the current versions of user guide and example: |
docs/userguide/constraints.md
Outdated
2. **Interpoint constraints:** These constraints model the relationship of different experiments | ||
across the whole batch (*inter-*) of exeriments (*-points*). | ||
|
||
#### Intrapoint constraints |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
results_df = pd.DataFrame(results_log) | ||
|
||
# fmt: off | ||
fig, axs = plt.subplots(1, 2, figsize=(10, 4)); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 theBudget
a570f35
to
03280fb
Compare
43e3257
to
1e7ff6d
Compare
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