Rename Variables and Constraints; move to internal.py

master
Alinson S. Xavier 4 years ago
parent c8c29138ca
commit 609c5c7694
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -12,7 +12,8 @@ from miplearn.classifiers import Classifier
from miplearn.classifiers.counting import CountingClassifier from miplearn.classifiers.counting import CountingClassifier
from miplearn.classifiers.threshold import MinProbabilityThreshold, Threshold from miplearn.classifiers.threshold import MinProbabilityThreshold, Threshold
from miplearn.components.component import Component from miplearn.components.component import Component
from miplearn.features import Sample, ConstraintFeatures from miplearn.features import Sample
from miplearn.solvers.internal import Constraints
from miplearn.instance.base import Instance from miplearn.instance.base import Instance
from miplearn.types import LearningSolveStats from miplearn.types import LearningSolveStats
@ -45,7 +46,7 @@ class StaticLazyConstraintsComponent(Component):
self.threshold_prototype: Threshold = threshold self.threshold_prototype: Threshold = threshold
self.classifiers: Dict[Hashable, Classifier] = {} self.classifiers: Dict[Hashable, Classifier] = {}
self.thresholds: Dict[Hashable, Threshold] = {} self.thresholds: Dict[Hashable, Threshold] = {}
self.pool: ConstraintFeatures = ConstraintFeatures() self.pool: Constraints = Constraints()
self.violation_tolerance: float = violation_tolerance self.violation_tolerance: float = violation_tolerance
self.enforced_cids: Set[Hashable] = set() self.enforced_cids: Set[Hashable] = set()
self.n_restored: int = 0 self.n_restored: int = 0
@ -82,7 +83,7 @@ class StaticLazyConstraintsComponent(Component):
logger.info("Instance does not have static lazy constraints. Skipping.") logger.info("Instance does not have static lazy constraints. Skipping.")
self.enforced_cids = set(self.sample_predict(sample)) self.enforced_cids = set(self.sample_predict(sample))
logger.info("Moving lazy constraints to the pool...") logger.info("Moving lazy constraints to the pool...")
constraints = ConstraintFeatures.from_sample(sample) constraints = Constraints.from_sample(sample)
assert constraints.lazy is not None assert constraints.lazy is not None
assert constraints.names is not None assert constraints.names is not None
selected = [ selected = [

@ -4,9 +4,8 @@
import collections import collections
import numbers import numbers
from dataclasses import dataclass
from math import log, isfinite from math import log, isfinite
from typing import TYPE_CHECKING, Dict, Optional, List, Hashable, Tuple, Any from typing import TYPE_CHECKING, Dict, Optional, List, Hashable, Any
import numpy as np import numpy as np
@ -15,76 +14,6 @@ if TYPE_CHECKING:
from miplearn.instance.base import Instance from miplearn.instance.base import Instance
@dataclass
class VariableFeatures:
names: Optional[List[str]] = None
basis_status: Optional[List[str]] = None
lower_bounds: Optional[List[float]] = None
obj_coeffs: Optional[List[float]] = None
reduced_costs: Optional[List[float]] = None
sa_lb_down: Optional[List[float]] = None
sa_lb_up: Optional[List[float]] = None
sa_obj_down: Optional[List[float]] = None
sa_obj_up: Optional[List[float]] = None
sa_ub_down: Optional[List[float]] = None
sa_ub_up: Optional[List[float]] = None
types: Optional[List[str]] = None
upper_bounds: Optional[List[float]] = None
values: Optional[List[float]] = None
@dataclass
class ConstraintFeatures:
basis_status: Optional[List[str]] = None
dual_values: Optional[List[float]] = None
lazy: Optional[List[bool]] = None
lhs: Optional[List[List[Tuple[str, float]]]] = None
names: Optional[List[str]] = None
rhs: Optional[List[float]] = None
sa_rhs_down: Optional[List[float]] = None
sa_rhs_up: Optional[List[float]] = None
senses: Optional[List[str]] = None
slacks: Optional[List[float]] = None
@staticmethod
def from_sample(sample: "Sample") -> "ConstraintFeatures":
return ConstraintFeatures(
basis_status=sample.get("lp_constr_basis_status"),
dual_values=sample.get("lp_constr_dual_values"),
lazy=sample.get("constr_lazy"),
lhs=sample.get("constr_lhs"),
names=sample.get("constr_names"),
rhs=sample.get("constr_rhs"),
sa_rhs_down=sample.get("lp_constr_sa_rhs_down"),
sa_rhs_up=sample.get("lp_constr_sa_rhs_up"),
senses=sample.get("constr_senses"),
slacks=sample.get("lp_constr_slacks"),
)
def __getitem__(self, selected: List[bool]) -> "ConstraintFeatures":
return ConstraintFeatures(
basis_status=self._filter(self.basis_status, selected),
dual_values=self._filter(self.dual_values, selected),
names=self._filter(self.names, selected),
lazy=self._filter(self.lazy, selected),
lhs=self._filter(self.lhs, selected),
rhs=self._filter(self.rhs, selected),
sa_rhs_down=self._filter(self.sa_rhs_down, selected),
sa_rhs_up=self._filter(self.sa_rhs_up, selected),
senses=self._filter(self.senses, selected),
slacks=self._filter(self.slacks, selected),
)
def _filter(
self,
obj: Optional[List],
selected: List[bool],
) -> Optional[List]:
if obj is None:
return None
return [obj[i] for (i, selected_i) in enumerate(selected) if selected_i]
class Sample: class Sample:
def __init__( def __init__(
self, self,

@ -10,7 +10,6 @@ from typing import List, Any, Dict, Optional, Hashable, Tuple, TYPE_CHECKING
from overrides import overrides from overrides import overrides
from miplearn.features import VariableFeatures, ConstraintFeatures
from miplearn.instance.base import Instance from miplearn.instance.base import Instance
from miplearn.solvers import _RedirectOutput from miplearn.solvers import _RedirectOutput
from miplearn.solvers.internal import ( from miplearn.solvers.internal import (
@ -19,6 +18,8 @@ from miplearn.solvers.internal import (
IterationCallback, IterationCallback,
LazyCallback, LazyCallback,
MIPSolveStats, MIPSolveStats,
Variables,
Constraints,
) )
from miplearn.solvers.pyomo.base import PyomoTestInstanceKnapsack from miplearn.solvers.pyomo.base import PyomoTestInstanceKnapsack
from miplearn.types import ( from miplearn.types import (
@ -91,7 +92,7 @@ class GurobiSolver(InternalSolver):
] ]
@overrides @overrides
def add_constraints(self, cf: ConstraintFeatures) -> None: def add_constraints(self, cf: Constraints) -> None:
assert cf.names is not None assert cf.names is not None
assert cf.senses is not None assert cf.senses is not None
assert cf.lhs is not None assert cf.lhs is not None
@ -120,7 +121,7 @@ class GurobiSolver(InternalSolver):
@overrides @overrides
def are_constraints_satisfied( def are_constraints_satisfied(
self, self,
cf: ConstraintFeatures, cf: Constraints,
tol: float = 1e-5, tol: float = 1e-5,
) -> List[bool]: ) -> List[bool]:
assert cf.names is not None assert cf.names is not None
@ -196,7 +197,7 @@ class GurobiSolver(InternalSolver):
with_static: bool = True, with_static: bool = True,
with_sa: bool = True, with_sa: bool = True,
with_lhs: bool = True, with_lhs: bool = True,
) -> ConstraintFeatures: ) -> Constraints:
model = self.model model = self.model
assert model is not None assert model is not None
assert model.numVars == len(self._gp_vars) assert model.numVars == len(self._gp_vars)
@ -241,7 +242,7 @@ class GurobiSolver(InternalSolver):
if self._has_lp_solution or self._has_mip_solution: if self._has_lp_solution or self._has_mip_solution:
slacks = model.getAttr("slack", gp_constrs) slacks = model.getAttr("slack", gp_constrs)
return ConstraintFeatures( return Constraints(
basis_status=basis_status, basis_status=basis_status,
dual_values=dual_value, dual_values=dual_value,
lhs=lhs, lhs=lhs,
@ -300,7 +301,7 @@ class GurobiSolver(InternalSolver):
self, self,
with_static: bool = True, with_static: bool = True,
with_sa: bool = True, with_sa: bool = True,
) -> VariableFeatures: ) -> Variables:
model = self.model model = self.model
assert model is not None assert model is not None
@ -347,7 +348,7 @@ class GurobiSolver(InternalSolver):
if model.solCount > 0: if model.solCount > 0:
values = model.getAttr("x", self._gp_vars) values = model.getAttr("x", self._gp_vars)
return VariableFeatures( return Variables(
names=self._var_names, names=self._var_names,
upper_bounds=upper_bounds, upper_bounds=upper_bounds,
lower_bounds=lower_bounds, lower_bounds=lower_bounds,

@ -5,9 +5,8 @@
import logging import logging
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, List, Optional, List from typing import Any, Optional, List, Tuple, TYPE_CHECKING
from miplearn.features import VariableFeatures, ConstraintFeatures
from miplearn.instance.base import Instance from miplearn.instance.base import Instance
from miplearn.types import ( from miplearn.types import (
IterationCallback, IterationCallback,
@ -18,6 +17,9 @@ from miplearn.types import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from miplearn.features import Sample
@dataclass @dataclass
class LPSolveStats: class LPSolveStats:
@ -44,20 +46,90 @@ class MIPSolveStats:
mip_warm_start_value: Optional[float] = None mip_warm_start_value: Optional[float] = None
@dataclass
class Variables:
names: Optional[List[str]] = None
basis_status: Optional[List[str]] = None
lower_bounds: Optional[List[float]] = None
obj_coeffs: Optional[List[float]] = None
reduced_costs: Optional[List[float]] = None
sa_lb_down: Optional[List[float]] = None
sa_lb_up: Optional[List[float]] = None
sa_obj_down: Optional[List[float]] = None
sa_obj_up: Optional[List[float]] = None
sa_ub_down: Optional[List[float]] = None
sa_ub_up: Optional[List[float]] = None
types: Optional[List[str]] = None
upper_bounds: Optional[List[float]] = None
values: Optional[List[float]] = None
@dataclass
class Constraints:
basis_status: Optional[List[str]] = None
dual_values: Optional[List[float]] = None
lazy: Optional[List[bool]] = None
lhs: Optional[List[List[Tuple[str, float]]]] = None
names: Optional[List[str]] = None
rhs: Optional[List[float]] = None
sa_rhs_down: Optional[List[float]] = None
sa_rhs_up: Optional[List[float]] = None
senses: Optional[List[str]] = None
slacks: Optional[List[float]] = None
@staticmethod
def from_sample(sample: "Sample") -> "Constraints":
return Constraints(
basis_status=sample.get("lp_constr_basis_status"),
dual_values=sample.get("lp_constr_dual_values"),
lazy=sample.get("constr_lazy"),
lhs=sample.get("constr_lhs"),
names=sample.get("constr_names"),
rhs=sample.get("constr_rhs"),
sa_rhs_down=sample.get("lp_constr_sa_rhs_down"),
sa_rhs_up=sample.get("lp_constr_sa_rhs_up"),
senses=sample.get("constr_senses"),
slacks=sample.get("lp_constr_slacks"),
)
def __getitem__(self, selected: List[bool]) -> "Constraints":
return Constraints(
basis_status=self._filter(self.basis_status, selected),
dual_values=self._filter(self.dual_values, selected),
names=self._filter(self.names, selected),
lazy=self._filter(self.lazy, selected),
lhs=self._filter(self.lhs, selected),
rhs=self._filter(self.rhs, selected),
sa_rhs_down=self._filter(self.sa_rhs_down, selected),
sa_rhs_up=self._filter(self.sa_rhs_up, selected),
senses=self._filter(self.senses, selected),
slacks=self._filter(self.slacks, selected),
)
def _filter(
self,
obj: Optional[List],
selected: List[bool],
) -> Optional[List]:
if obj is None:
return None
return [obj[i] for (i, selected_i) in enumerate(selected) if selected_i]
class InternalSolver(ABC): class InternalSolver(ABC):
""" """
Abstract class representing the MIP solver used internally by LearningSolver. Abstract class representing the MIP solver used internally by LearningSolver.
""" """
@abstractmethod @abstractmethod
def add_constraints(self, cf: ConstraintFeatures) -> None: def add_constraints(self, cf: Constraints) -> None:
"""Adds the given constraints to the model.""" """Adds the given constraints to the model."""
pass pass
@abstractmethod @abstractmethod
def are_constraints_satisfied( def are_constraints_satisfied(
self, self,
cf: ConstraintFeatures, cf: Constraints,
tol: float = 1e-5, tol: float = 1e-5,
) -> List[bool]: ) -> List[bool]:
""" """
@ -133,7 +205,7 @@ class InternalSolver(ABC):
with_static: bool = True, with_static: bool = True,
with_sa: bool = True, with_sa: bool = True,
with_lhs: bool = True, with_lhs: bool = True,
) -> ConstraintFeatures: ) -> Constraints:
pass pass
@abstractmethod @abstractmethod
@ -149,7 +221,7 @@ class InternalSolver(ABC):
self, self,
with_static: bool = True, with_static: bool = True,
with_sa: bool = True, with_sa: bool = True,
) -> VariableFeatures: ) -> Variables:
""" """
Returns a description of the decision variables in the problem. Returns a description of the decision variables in the problem.

@ -19,7 +19,6 @@ from pyomo.core.expr.numeric_expr import SumExpression, MonomialTermExpression
from pyomo.opt import TerminationCondition from pyomo.opt import TerminationCondition
from pyomo.opt.base.solvers import SolverFactory from pyomo.opt.base.solvers import SolverFactory
from miplearn.features import VariableFeatures, ConstraintFeatures
from miplearn.instance.base import Instance from miplearn.instance.base import Instance
from miplearn.solvers import _RedirectOutput, _none_if_empty from miplearn.solvers import _RedirectOutput, _none_if_empty
from miplearn.solvers.internal import ( from miplearn.solvers.internal import (
@ -28,6 +27,8 @@ from miplearn.solvers.internal import (
IterationCallback, IterationCallback,
LazyCallback, LazyCallback,
MIPSolveStats, MIPSolveStats,
Variables,
Constraints,
) )
from miplearn.types import ( from miplearn.types import (
SolverParams, SolverParams,
@ -79,7 +80,7 @@ class BasePyomoSolver(InternalSolver):
self._has_mip_solution = False self._has_mip_solution = False
@overrides @overrides
def add_constraints(self, cf: ConstraintFeatures) -> None: def add_constraints(self, cf: Constraints) -> None:
assert cf.names is not None assert cf.names is not None
assert cf.senses is not None assert cf.senses is not None
assert cf.lhs is not None assert cf.lhs is not None
@ -111,7 +112,7 @@ class BasePyomoSolver(InternalSolver):
@overrides @overrides
def are_constraints_satisfied( def are_constraints_satisfied(
self, self,
cf: ConstraintFeatures, cf: Constraints,
tol: float = 1e-5, tol: float = 1e-5,
) -> List[bool]: ) -> List[bool]:
assert cf.names is not None assert cf.names is not None
@ -159,7 +160,7 @@ class BasePyomoSolver(InternalSolver):
with_static: bool = True, with_static: bool = True,
with_sa: bool = True, with_sa: bool = True,
with_lhs: bool = True, with_lhs: bool = True,
) -> ConstraintFeatures: ) -> Constraints:
model = self.model model = self.model
assert model is not None assert model is not None
@ -233,7 +234,7 @@ class BasePyomoSolver(InternalSolver):
names.append(constr.name) names.append(constr.name)
_parse_constraint(constr) _parse_constraint(constr)
return ConstraintFeatures( return Constraints(
names=_none_if_empty(names), names=_none_if_empty(names),
rhs=_none_if_empty(rhs), rhs=_none_if_empty(rhs),
senses=_none_if_empty(senses), senses=_none_if_empty(senses),
@ -271,7 +272,7 @@ class BasePyomoSolver(InternalSolver):
self, self,
with_static: bool = True, with_static: bool = True,
with_sa: bool = True, with_sa: bool = True,
) -> VariableFeatures: ) -> Variables:
assert self.model is not None assert self.model is not None
names: List[str] = [] names: List[str] = []
@ -326,7 +327,7 @@ class BasePyomoSolver(InternalSolver):
if self._has_lp_solution or self._has_mip_solution: if self._has_lp_solution or self._has_mip_solution:
values.append(v.value) values.append(v.value)
return VariableFeatures( return Variables(
names=_none_if_empty(names), names=_none_if_empty(names),
types=_none_if_empty(types), types=_none_if_empty(types),
upper_bounds=_none_if_empty(upper_bounds), upper_bounds=_none_if_empty(upper_bounds),

@ -6,8 +6,7 @@ from typing import Any, List
import numpy as np import numpy as np
from miplearn.features import VariableFeatures, ConstraintFeatures from miplearn.solvers.internal import InternalSolver, Variables, Constraints
from miplearn.solvers.internal import InternalSolver
inf = float("inf") inf = float("inf")
@ -40,7 +39,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
# Fetch variables (after-load) # Fetch variables (after-load)
assert_equals( assert_equals(
solver.get_variables(), solver.get_variables(),
VariableFeatures( Variables(
names=["x[0]", "x[1]", "x[2]", "x[3]", "z"], names=["x[0]", "x[1]", "x[2]", "x[3]", "z"],
lower_bounds=[0.0, 0.0, 0.0, 0.0, 0.0], lower_bounds=[0.0, 0.0, 0.0, 0.0, 0.0],
upper_bounds=[1.0, 1.0, 1.0, 1.0, 67.0], upper_bounds=[1.0, 1.0, 1.0, 1.0, 67.0],
@ -52,7 +51,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
# Fetch constraints (after-load) # Fetch constraints (after-load)
assert_equals( assert_equals(
solver.get_constraints(), solver.get_constraints(),
ConstraintFeatures( Constraints(
names=["eq_capacity"], names=["eq_capacity"],
rhs=[0.0], rhs=[0.0],
lhs=[ lhs=[
@ -83,7 +82,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_variables(with_static=False), solver.get_variables(with_static=False),
_filter_attrs( _filter_attrs(
solver.get_variable_attrs(), solver.get_variable_attrs(),
VariableFeatures( Variables(
names=["x[0]", "x[1]", "x[2]", "x[3]", "z"], names=["x[0]", "x[1]", "x[2]", "x[3]", "z"],
basis_status=["U", "B", "U", "L", "U"], basis_status=["U", "B", "U", "L", "U"],
reduced_costs=[193.615385, 0.0, 187.230769, -23.692308, 13.538462], reduced_costs=[193.615385, 0.0, 187.230769, -23.692308, 13.538462],
@ -103,7 +102,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_constraints(with_static=False), solver.get_constraints(with_static=False),
_filter_attrs( _filter_attrs(
solver.get_constraint_attrs(), solver.get_constraint_attrs(),
ConstraintFeatures( Constraints(
basis_status=["N"], basis_status=["N"],
dual_values=[13.538462], dual_values=[13.538462],
names=["eq_capacity"], names=["eq_capacity"],
@ -136,7 +135,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_variables(with_static=False), solver.get_variables(with_static=False),
_filter_attrs( _filter_attrs(
solver.get_variable_attrs(), solver.get_variable_attrs(),
VariableFeatures( Variables(
names=["x[0]", "x[1]", "x[2]", "x[3]", "z"], names=["x[0]", "x[1]", "x[2]", "x[3]", "z"],
values=[1.0, 0.0, 1.0, 1.0, 61.0], values=[1.0, 0.0, 1.0, 1.0, 61.0],
), ),
@ -148,7 +147,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_constraints(with_static=False), solver.get_constraints(with_static=False),
_filter_attrs( _filter_attrs(
solver.get_constraint_attrs(), solver.get_constraint_attrs(),
ConstraintFeatures( Constraints(
names=["eq_capacity"], names=["eq_capacity"],
slacks=[0.0], slacks=[0.0],
), ),
@ -156,7 +155,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
) )
# Build new constraint and verify that it is violated # Build new constraint and verify that it is violated
cf = ConstraintFeatures( cf = Constraints(
names=["cut"], names=["cut"],
lhs=[[("x[0]", 1.0)]], lhs=[[("x[0]", 1.0)]],
rhs=[0.0], rhs=[0.0],
@ -170,7 +169,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_constraints(with_static=True), solver.get_constraints(with_static=True),
_filter_attrs( _filter_attrs(
solver.get_constraint_attrs(), solver.get_constraint_attrs(),
ConstraintFeatures( Constraints(
names=["eq_capacity", "cut"], names=["eq_capacity", "cut"],
rhs=[0.0, 0.0], rhs=[0.0, 0.0],
lhs=[ lhs=[

@ -11,9 +11,9 @@ from numpy.testing import assert_array_equal
from miplearn.classifiers import Classifier from miplearn.classifiers import Classifier
from miplearn.classifiers.threshold import Threshold, MinProbabilityThreshold from miplearn.classifiers.threshold import Threshold, MinProbabilityThreshold
from miplearn.components.static_lazy import StaticLazyConstraintsComponent from miplearn.components.static_lazy import StaticLazyConstraintsComponent
from miplearn.features import Sample, ConstraintFeatures from miplearn.features import Sample
from miplearn.instance.base import Instance from miplearn.instance.base import Instance
from miplearn.solvers.internal import InternalSolver from miplearn.solvers.internal import InternalSolver, Constraints
from miplearn.solvers.learning import LearningSolver from miplearn.solvers.learning import LearningSolver
from miplearn.types import ( from miplearn.types import (
LearningSolveStats, LearningSolveStats,
@ -118,7 +118,7 @@ def test_usage_with_solver(instance: Instance) -> None:
# Should ask internal solver to verify if constraints in the pool are # Should ask internal solver to verify if constraints in the pool are
# satisfied and add the ones that are not # satisfied and add the ones that are not
c = ConstraintFeatures.from_sample(sample)[[False, False, True, False, False]] c = Constraints.from_sample(sample)[[False, False, True, False, False]]
internal.are_constraints_satisfied.assert_called_once_with(c, tol=1.0) internal.are_constraints_satisfied.assert_called_once_with(c, tol=1.0)
internal.are_constraints_satisfied.reset_mock() internal.are_constraints_satisfied.reset_mock()
internal.add_constraints.assert_called_once_with(c) internal.add_constraints.assert_called_once_with(c)

@ -6,10 +6,9 @@ import numpy as np
from miplearn.features import ( from miplearn.features import (
FeaturesExtractor, FeaturesExtractor,
VariableFeatures,
ConstraintFeatures,
Sample, Sample,
) )
from miplearn.solvers.internal import Variables, Constraints
from miplearn.solvers.gurobi import GurobiSolver from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.tests import assert_equals from miplearn.solvers.tests import assert_equals
@ -129,7 +128,7 @@ def test_knapsack() -> None:
def test_constraint_getindex() -> None: def test_constraint_getindex() -> None:
cf = ConstraintFeatures( cf = Constraints(
names=["c1", "c2", "c3"], names=["c1", "c2", "c3"],
rhs=[1.0, 2.0, 3.0], rhs=[1.0, 2.0, 3.0],
senses=["=", "<", ">"], senses=["=", "<", ">"],
@ -150,7 +149,7 @@ def test_constraint_getindex() -> None:
) )
assert_equals( assert_equals(
cf[[True, False, True]], cf[[True, False, True]],
ConstraintFeatures( Constraints(
names=["c1", "c3"], names=["c1", "c3"],
rhs=[1.0, 3.0], rhs=[1.0, 3.0],
senses=["=", ">"], senses=["=", ">"],
@ -177,8 +176,8 @@ def test_assert_equals() -> None:
np.array([[1.0, 2.0], [3.0, 4.0]]), np.array([[1.0, 2.0], [3.0, 4.0]]),
) )
assert_equals( assert_equals(
VariableFeatures(values=np.array([1.0, 2.0])), # type: ignore Variables(values=np.array([1.0, 2.0])), # type: ignore
VariableFeatures(values=np.array([1.0, 2.0])), # type: ignore Variables(values=np.array([1.0, 2.0])), # type: ignore
) )
assert_equals(np.array([True, True]), [True, True]) assert_equals(np.array([True, True]), [True, True])
assert_equals((1.0,), (1.0,)) assert_equals((1.0,), (1.0,))

Loading…
Cancel
Save