Skip to content

Commit 59bbd57

Browse files
authored
pydantic 2 - migration (#279)
1 parent 33a2053 commit 59bbd57

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+672
-640
lines changed

bofire/benchmarks/multi.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import numpy as np
66
import pandas as pd
77
import torch
8-
from pydantic import validator
8+
from pydantic import field_validator
99
from pydantic.types import PositiveInt
1010
from scipy.integrate import solve_ivp
1111
from scipy.special import gamma
@@ -65,7 +65,8 @@ def __init__(self, dim: PositiveInt, num_objectives: PositiveInt = 2, **kwargs):
6565
}
6666
self._domain = domain
6767

68-
@validator("dim")
68+
@field_validator("dim")
69+
@classmethod
6970
def validate_dim(cls, dim, values):
7071
num_objectives = values["num_objectives"]
7172
if dim <= values["num_objectives"]:

bofire/data_models/base.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import pandas as pd
22
from pydantic import BaseModel as PydanticBaseModel
3-
from pydantic import Extra
3+
from pydantic import ConfigDict
44

55

66
class BaseModel(PydanticBaseModel):
7-
class Config:
8-
validate_assignment = True
9-
arbitrary_types_allowed = False
10-
copy_on_model_validation = "none"
11-
extra = Extra.forbid
12-
13-
json_encoders = {
7+
# json_encoders is deprecated.
8+
# Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information.
9+
model_config = ConfigDict(
10+
validate_assignment=True,
11+
arbitrary_types_allowed=False,
12+
extra="forbid",
13+
json_encoders={
1414
pd.DataFrame: lambda x: x.to_dict(orient="list"),
1515
pd.Series: lambda x: x.to_list(),
16-
}
16+
},
17+
)

bofire/data_models/constraints/constraint.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,5 @@ class ConstraintNotFulfilledError(ConstraintError):
7272
pass
7373

7474

75-
FeatureKeys = Annotated[List[str], Field(min_items=2)]
76-
Coefficients = Annotated[List[float], Field(min_items=2)]
75+
FeatureKeys = Annotated[List[str], Field(min_length=2)]
76+
Coefficients = Annotated[List[float], Field(min_length=2)]

bofire/data_models/constraints/interpoint.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class InterpointEqualityConstraint(InterpointConstraint):
2828

2929
type: Literal["InterpointEqualityConstraint"] = "InterpointEqualityConstraint"
3030
feature: str
31-
multiplicity: Optional[Annotated[int, Field(ge=2)]]
31+
multiplicity: Optional[Annotated[int, Field(ge=2)]] = None
3232

3333
def is_fulfilled(
3434
self, experiments: pd.DataFrame, tol: Optional[float] = 1e-6

bofire/data_models/constraints/linear.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import numpy as np
44
import pandas as pd
5-
from pydantic import root_validator, validator
5+
from pydantic import field_validator, model_validator
66

77
from bofire.data_models.constraints.constraint import (
88
Coefficients,
@@ -26,21 +26,22 @@ class LinearConstraint(IntrapointConstraint):
2626
coefficients: Coefficients
2727
rhs: float
2828

29-
@validator("features")
29+
@field_validator("features")
30+
@classmethod
3031
def validate_features_unique(cls, features):
3132
"""Validate that feature keys are unique."""
3233
if len(features) != len(set(features)):
3334
raise ValueError("features must be unique")
3435
return features
3536

36-
@root_validator(pre=False, skip_on_failure=True)
37-
def validate_list_lengths(cls, values):
37+
@model_validator(mode="after")
38+
def validate_list_lengths(self):
3839
"""Validate that length of the feature and coefficient lists have the same length."""
39-
if len(values["features"]) != len(values["coefficients"]):
40+
if len(self.features) != len(self.coefficients):
4041
raise ValueError(
41-
f'must provide same number of features and coefficients, got {len(values["features"])} != {len(values["coefficients"])}'
42+
f"must provide same number of features and coefficients, got {len(self.features)} != {len(self.coefficients)}"
4243
)
43-
return values
44+
return self
4445

4546
def __call__(self, experiments: pd.DataFrame) -> pd.Series:
4647
return (

bofire/data_models/constraints/nchoosek.py

+17-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import numpy as np
44
import pandas as pd
5-
from pydantic import root_validator, validator
5+
from pydantic import field_validator, model_validator
66

77
from bofire.data_models.constraints.constraint import FeatureKeys, IntrapointConstraint
88

@@ -28,28 +28,26 @@ class NChooseKConstraint(IntrapointConstraint):
2828
max_count: int
2929
none_also_valid: bool
3030

31-
@validator("features")
31+
@field_validator("features")
32+
@classmethod
3233
def validate_features_unique(cls, features: List[str]):
3334
"""Validates that provided feature keys are unique."""
3435
if len(features) != len(set(features)):
3536
raise ValueError("features must be unique")
3637
return features
3738

38-
@root_validator(pre=False, skip_on_failure=True)
39-
def validate_counts(cls, values):
39+
@model_validator(mode="after")
40+
def validate_counts(self):
4041
"""Validates if the minimum and maximum of allowed features are smaller than the overall number of features."""
41-
features = values["features"]
42-
min_count = values["min_count"]
43-
max_count = values["max_count"]
4442

45-
if min_count > len(features):
43+
if self.min_count > len(self.features):
4644
raise ValueError("min_count must be <= # of features")
47-
if max_count > len(features):
45+
if self.max_count > len(self.features):
4846
raise ValueError("max_count must be <= # of features")
49-
if min_count > max_count:
47+
if self.min_count > self.max_count:
5048
raise ValueError("min_values must be <= max_values")
5149

52-
return values
50+
return self
5351

5452
def __call__(self, experiments: pd.DataFrame) -> pd.Series:
5553
"""Smooth relaxation of NChooseK constraint by countig the number of zeros in a candidate by a sum of
@@ -75,10 +73,16 @@ def relu(x):
7573
min_count_violation = np.zeros(experiments_tensor.shape[0])
7674

7775
if self.max_count != len(self.features):
78-
max_count_violation = relu(-1 * narrow_gaussian(x=experiments_tensor[..., indices]).sum(axis=-1) + (len(self.features) - self.max_count)) # type: ignore
76+
max_count_violation = relu(
77+
-1 * narrow_gaussian(x=experiments_tensor[..., indices]).sum(axis=-1)
78+
+ (len(self.features) - self.max_count)
79+
)
7980

8081
if self.min_count > 0:
81-
min_count_violation = relu(narrow_gaussian(x=experiments_tensor[..., indices]).sum(axis=-1) - (len(self.features) - self.min_count)) # type: ignore
82+
min_count_violation = relu(
83+
narrow_gaussian(x=experiments_tensor[..., indices]).sum(axis=-1)
84+
- (len(self.features) - self.min_count)
85+
)
8286

8387
return pd.Series(max_count_violation + min_count_violation)
8488

bofire/data_models/constraints/nonlinear.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import numpy as np
55
import pandas as pd
6-
from pydantic import validator
6+
from pydantic import Field, field_validator
77

88
from bofire.data_models.constraints.constraint import FeatureKeys, IntrapointConstraint
99

@@ -19,10 +19,11 @@ class NonlinearConstraint(IntrapointConstraint):
1919

2020
expression: str
2121
features: Optional[FeatureKeys] = None
22-
jacobian_expression: Optional[str] = None
22+
jacobian_expression: Optional[str] = Field(default=None, validate_default=True)
2323

24-
@validator("jacobian_expression", always=True)
25-
def set_jacobian_expression(cls, jacobian_expression, values):
24+
@field_validator("jacobian_expression")
25+
@classmethod
26+
def set_jacobian_expression(cls, jacobian_expression, info):
2627
try:
2728
import sympy # type: ignore
2829
except ImportError as e:
@@ -32,16 +33,16 @@ def set_jacobian_expression(cls, jacobian_expression, values):
3233

3334
if (
3435
jacobian_expression is None
35-
and "features" in values
36-
and "expression" in values
36+
and "features" in info.data.keys()
37+
and "expression" in info.data.keys()
3738
):
38-
if values["features"] is not None:
39+
if info.data["features"] is not None:
3940
return (
4041
"["
4142
+ ", ".join(
4243
[
43-
str(sympy.S(values["expression"]).diff(key))
44-
for key in values["features"]
44+
str(sympy.S(info.data["expression"]).diff(key))
45+
for key in info.data["features"]
4546
]
4647
)
4748
+ "]"

bofire/data_models/domain/domain.py

+32-46
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import numpy as np
1919
import pandas as pd
20-
from pydantic import Field, validator
20+
from pydantic import Field, field_validator, model_validator
2121

2222
from bofire.data_models.base import BaseModel
2323
from bofire.data_models.constraints.api import (
@@ -57,7 +57,6 @@ class Domain(BaseModel):
5757

5858
inputs: Inputs = Field(default_factory=lambda: Inputs())
5959
outputs: Outputs = Field(default_factory=lambda: Outputs())
60-
6160
constraints: Constraints = Field(default_factory=lambda: Constraints())
6261

6362
"""Representation of the optimization problem/domain
@@ -84,8 +83,9 @@ def from_lists(
8483
constraints=Constraints(constraints=constraints),
8584
)
8685

87-
@validator("inputs", always=True, pre=True)
88-
def validate_inputs_list(cls, v, values):
86+
@field_validator("inputs", mode="before")
87+
@classmethod
88+
def validate_inputs_list(cls, v):
8989
if isinstance(v, collections.abc.Sequence):
9090
v = Inputs(features=v)
9191
return v
@@ -94,26 +94,28 @@ def validate_inputs_list(cls, v, values):
9494
else:
9595
return v
9696

97-
@validator("outputs", always=True, pre=True)
98-
def validate_outputs_list(cls, v, values):
97+
@field_validator("outputs", mode="before")
98+
@classmethod
99+
def validate_outputs_list(cls, v):
99100
if isinstance(v, collections.abc.Sequence):
100101
return Outputs(features=v)
101102
if isinstance_or_union(v, AnyOutput):
102103
return Outputs(features=[v])
103104
else:
104105
return v
105106

106-
@validator("constraints", always=True, pre=True)
107-
def validate_constraints_list(cls, v, values):
107+
@field_validator("constraints", mode="before")
108+
@classmethod
109+
def validate_constraints_list(cls, v):
108110
if isinstance(v, list):
109111
return Constraints(constraints=v)
110112
if isinstance_or_union(v, AnyConstraint):
111113
return Constraints(constraints=[v])
112114
else:
113115
return v
114116

115-
@validator("outputs", always=True)
116-
def validate_unique_feature_keys(cls, v: Outputs, values) -> Outputs:
117+
@model_validator(mode="after")
118+
def validate_unique_feature_keys(self):
117119
"""Validates if provided input and output feature keys are unique
118120
119121
Args:
@@ -126,16 +128,14 @@ def validate_unique_feature_keys(cls, v: Outputs, values) -> Outputs:
126128
Returns:
127129
Outputs: Keeps output features as given.
128130
"""
129-
if "inputs" not in values:
130-
return v
131-
features = v + values["inputs"]
132-
keys = [f.key for f in features]
131+
132+
keys = self.outputs.get_keys() + self.inputs.get_keys()
133133
if len(set(keys)) != len(keys):
134-
raise ValueError("feature keys are not unique")
135-
return v
134+
raise ValueError("Feature keys are not unique")
135+
return self
136136

137-
@validator("constraints", always=True)
138-
def validate_constraints(cls, v, values):
137+
@model_validator(mode="after")
138+
def validate_constraints(self):
139139
"""Validate if all features included in the constraints are also defined as features for the domain.
140140
141141
Args:
@@ -148,18 +148,17 @@ def validate_constraints(cls, v, values):
148148
Returns:
149149
List[Constraint]: List of constraints defined for the domain
150150
"""
151-
if "inputs" not in values:
152-
return v
153-
keys = [f.key for f in values["inputs"]]
154-
for c in v:
151+
152+
keys = self.inputs.get_keys()
153+
for c in self.constraints:
155154
if isinstance(c, LinearConstraint) or isinstance(c, NChooseKConstraint):
156155
for f in c.features:
157156
if f not in keys:
158157
raise ValueError(f"feature {f} in constraint unknown ({keys})")
159-
return v
158+
return self
160159

161-
@validator("constraints", always=True)
162-
def validate_linear_constraints(cls, v, values):
160+
@model_validator(mode="after")
161+
def validate_linear_constraints_and_nchoosek(self):
163162
"""Validate if all features included in linear constraints are continuous ones.
164163
165164
Args:
@@ -173,21 +172,13 @@ def validate_linear_constraints(cls, v, values):
173172
Returns:
174173
List[Constraint]: List of constraints defined for the domain
175174
"""
176-
if "inputs" not in values:
177-
return v
178-
179-
# gather continuous inputs in dictionary
180-
continuous_inputs_dict = {}
181-
for f in values["inputs"]:
182-
if isinstance(f, ContinuousInput):
183-
continuous_inputs_dict[f.key] = f
175+
keys = self.inputs.get_keys(ContinuousInput)
184176

185177
# check if non continuous input features appear in linear constraints
186-
for c in v:
187-
if isinstance(c, LinearConstraint):
188-
for f in c.features:
189-
assert f in continuous_inputs_dict, f"{f} must be continuous."
190-
return v
178+
for c in self.constraints.get(includes=[LinearConstraint, NChooseKConstraint]):
179+
for f in c.features: # type: ignore
180+
assert f in keys, f"{f} must be continuous."
181+
return self
191182

192183
def get_feature_reps_df(self) -> pd.DataFrame:
193184
"""Returns a pandas dataframe describing the features contained in the optimization domain."""
@@ -617,11 +608,6 @@ def candidate_column_names(self):
617608
]
618609
)
619610

620-
def _set_constraints_unvalidated(
621-
self, constraints: Union[Sequence[AnyConstraint], Constraints]
622-
):
623-
"""Hack for reduce_domain"""
624-
self.constraints = Constraints(constraints=[])
625-
if isinstance(constraints, Constraints):
626-
constraints = constraints.constraints
627-
self.constraints.constraints = constraints
611+
612+
if __name__ == "__main__":
613+
pass

0 commit comments

Comments
 (0)