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

Add generic typing to Constraints #386

Merged
merged 4 commits into from
Mar 27, 2024

Conversation

TobyBoyne
Copy link
Collaborator

Following on from #383, here's generic typing in Constraints!

Pyright (and whatever type checker your IDE uses) will now infer the type of constraints contained in a Constraints object.

It isn't perfect - you can't type hint set subtraction [1], so the excludes keyword isn't typed properly. But it clears up more # type: ignores than before!

constraints = Constraints(constraints=[LinearEqualityConstraint(...), NChooseKConstraint(...)]))
# type of constraints.features is inferred as Sequence[LinearEqualityConstraint | NChooseKConstraint]
sub_constraints = constraints.get(includes=[NChooseKConstraint])
# type of sub_constraints.features is inferred as Sequence[NChooseKConstraint]

[1] https://discuss.python.org/t/type-intersection-and-negation-in-type-annotations/23879

@TobyBoyne TobyBoyne requested a review from bertiqwerty March 22, 2024 17:28
@jduerholt
Copy link
Contributor

Nice, thanks a lot @TobyBoyne! Can you perhaps elaborate a bit on what you actually did and how it works, as I do not understand why it works ;) So, I think it is better when @bertiqwerty reviews it. If it is fine for him, can you then also file a similar PR for Features, Inputs, and Outputs.

We should then also remove the following functions from domain as they are obsolete and the corresponding versions from domain.inputs, domain.outputs and domain.constraints should be used:

  • domain.get_features
  • domain.get_feature_keys
  • get_feature

Or what do you think @bertiqwerty ?

@TobyBoyne
Copy link
Collaborator Author

TobyBoyne commented Mar 26, 2024

Thanks @jduerholt!

Of course! Just to note, this does not change how users interface with bofire. The only way that users will interact with this is by seeing that their type hints have automatically become more informative! Also, this has already been implemented for Features, Inputs, and Outputs in #383.

So this PR uses Generic typing. You will have used generic type hints whenever you do something like

my_list: List[int]
x = my_list[0]
# the type of x is automatically inferred to be `int`

In this example, List accepts int as a TypeVar, which determines the type of the elements in the list.

I've done something similar here. You can now create a Constraints object that contains only one type of constraint, like Constraint[NChooseKConstraint].

However, you don't want to manually type hint the type of constraints each time. Going back to the previous example, we want something more like

my_list = [2, 4, 8]
x = my_list[0]
# the type of x is automatically inferred to be `int`, no explicit type hint required!

This is where TypeVar is useful. This allows is to infer the type of the elements in Constraint.constraints using generics. Whenever you make a new instance of a Constraint object, the TypeVar C stores the type of constraint passed to the constructor. This means that when you iterate through the constraints, your typechecker knows that the items you iterate over will be limited to the type of constraints that were passed to the input.

We can do even better. We use a second TypeVar, C_, for use in the get method. This allows us to keep track of the types of constraint passed to the includes argument, so that we can further restrict the type of constraint in the returned object. So now, when we have code like below:

constraints = Constraints(constraints=[...])
for constraint in constraints.get(includes=[NChooseKConstraint]):
    print(constraint.max_count)
   # Normally, pyright would raise an error here, since AnyConstraint.max_count isn't defined.
   # But with generic types, pyright can infer that the returned constraints from the `get` call
   #   must be of type NChooseKConstraint, so this attribute is always defined!

I hope this clears things up :) Let me know if you have any other questions!

@bertiqwerty
Copy link
Contributor

We should then also remove the following functions from domain as they are obsolete and the corresponding versions from domain.inputs, domain.outputs and domain.constraints should be used:

* `domain.get_features`

* `domain.get_feature_keys`

* `get_feature`

Or what do you think @bertiqwerty ?

Agreed. In a separate PR.

Copy link
Contributor

@bertiqwerty bertiqwerty left a comment

Choose a reason for hiding this comment

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

Thanks, Toby. Please check my review comments and merge afterwards.

bofire/data_models/domain/constraints.py Show resolved Hide resolved
exact: bool = False,
) -> "Constraints":
) -> "Constraints[C_]":
"""get constraints of the domain

Args:
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you remove the types from the comments? They are wrong now and we don't need them anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

When you say we don't need types in the docstrings, are they not used to generate the docs? For example here, the docs list the return type of the get method according to the docstring, even though this type differs from the return type in the function signature.

Copy link
Contributor

Choose a reason for hiding this comment

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

If the doc-extractor is using the types in the docstrings we should fix the doc-extractor.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point. Shall I leave the comments in for now, and then leave that to another PR? Or still take out the comments?

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove them. :)

@@ -34,7 +34,7 @@

def get_linear_constraints(
domain: Domain,
constraint: Union[LinearEqualityConstraint, LinearInequalityConstraint],
constraint: Union[Type[LinearEqualityConstraint], Type[LinearInequalityConstraint]],
unit_scaled: bool = False,
) -> List[Tuple[Tensor, Tensor, float]]:
"""Converts linear constraints to the form required by BoTorch.
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, please remove out-dated types from comments.

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've updated the signature, but please see other comment about keeping types in docstrings.

@TobyBoyne TobyBoyne merged commit 3e37f47 into experimental-design:main Mar 27, 2024
10 checks passed
@TobyBoyne TobyBoyne deleted the typingconstraints branch March 27, 2024 12:25
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