From b866af408bb5f19801133e30e44962fa0b7323db Mon Sep 17 00:00:00 2001 From: iopapamanoglou Date: Fri, 15 Nov 2024 18:45:58 +0100 Subject: [PATCH] Implement get_known_superranges --- src/faebryk/core/defaultsolver.py | 106 ++++++++++++++---- src/faebryk/core/parameter.py | 32 +++--- src/faebryk/library/_F.py | 4 +- ...ue_representation_based_on_params_chain.py | 2 +- test/core/test_parameters.py | 2 +- 5 files changed, 107 insertions(+), 39 deletions(-) diff --git a/src/faebryk/core/defaultsolver.py b/src/faebryk/core/defaultsolver.py index 5f5ac5a0..e5168363 100644 --- a/src/faebryk/core/defaultsolver.py +++ b/src/faebryk/core/defaultsolver.py @@ -14,16 +14,19 @@ from faebryk.core.parameter import ( Add, And, - Arithmetic, ConstrainableExpression, Divide, Domain, Expression, + GreaterOrEqual, Is, IsSubset, + LessOrEqual, Multiply, + Numbers, Or, Parameter, + ParameterOperableHasNoLiteral, ParameterOperatable, Power, Predicate, @@ -31,7 +34,7 @@ has_implicit_constraints_recursive, ) from faebryk.core.solver import Solver -from faebryk.libs.sets import P_Set, Range, Ranges +from faebryk.libs.sets import P_Set, Range, Ranges, Single from faebryk.libs.units import HasUnit, Quantity, dimensionless from faebryk.libs.util import EquivalenceClasses, unique_ref @@ -892,7 +895,9 @@ def known_unconstrained(po: ParameterOperatable) -> bool: class DefaultSolver(Solver): timeout: int = 1000 - def phase_one_no_guess_solving(self, g: Graph) -> None: + def phase_one_no_guess_solving( + self, g: Graph + ) -> dict[ParameterOperatable, ParameterOperatable]: logger.info(f"Phase 1 Solving: No guesses {'-' * 80}") # TODO move into comment here @@ -988,6 +993,9 @@ def phase_one_no_guess_solving(self, g: Graph) -> None: or subset_dirty ) + # FIXME repr map should contain map from first to last + return repr_map + def get_any_single( self, operatable: ParameterOperatable, @@ -997,15 +1005,6 @@ def get_any_single( ) -> Any: raise NotImplementedError() - def assert_any_predicate[ArgType]( - self, - predicates: list["Solver.PredicateWithInfo[ArgType]"], - lock: bool, - suppose_constraint: Predicate | None = None, - minimize: Expression | None = None, - ) -> Solver.SolveResultAny[ArgType]: - raise NotImplementedError() - def find_and_lock_solution(self, G: Graph) -> Solver.SolveResultAll: raise NotImplementedError() @@ -1025,16 +1024,85 @@ def inspect_known_values( ) -> P_Set[bool]: raise NotImplementedError() - # Could be exponentially many - def inspect_known_supersets_are_few(self, value: ParameterOperatable.Sets) -> bool: - raise NotImplementedError() - def inspect_get_known_supersets( self, value: ParameterOperatable.Sets ) -> Iterable[P_Set]: raise NotImplementedError() - def inspect_get_known_superranges( - self, value: ParameterOperatable.NumberLike - ) -> Ranges: + # IMPORTANT ------------------------------------------------------------------------ + + # Could be exponentially many + def inspect_known_supersets_are_few(self, value: ParameterOperatable.Sets) -> bool: + return True + + def inspect_get_known_superranges(self, value: ParameterOperatable) -> Ranges: + if not isinstance(value.domain, Numbers): + raise ValueError(f"Ranges only defined for numbers not {value.domain}") + + # run phase 1 solver + # TODO caching + repr_map = self.phase_one_no_guess_solving(value.get_graph()) + value = repr_map[value] + + # check predicates (is, subset, greater, less) + try: + literal = value.get_literal(Is) + if Parameter.is_number_literal(literal): + return Ranges(Single(literal)) + if isinstance(literal, P_Set): + if isinstance(literal, Range): + return Ranges(literal) + if isinstance(literal, Ranges): + return literal + except ParameterOperableHasNoLiteral: + pass + + try: + literal = value.get_literal(IsSubset) + if Parameter.is_number_literal(literal): + return Ranges(Single(literal)) + if isinstance(literal, P_Set): + if isinstance(literal, Range): + return Ranges(literal) + if isinstance(literal, Ranges): + return literal + except ParameterOperableHasNoLiteral: + pass + + # TODO LT, GT + lower = None + try: + literal = value.get_literal(GreaterOrEqual) + if Parameter.is_number_literal(literal): + lower = literal + elif isinstance(literal, P_Set): + if isinstance(literal, Range): + lower = literal.max_elem() + elif isinstance(literal, Ranges): + lower = literal.max_elem() + except ParameterOperableHasNoLiteral: + lower = float("-inf") * HasUnit.get_units(value) + + upper = None + try: + literal = value.get_literal(LessOrEqual) + if Parameter.is_number_literal(literal): + upper = literal + elif isinstance(literal, P_Set): + if isinstance(literal, Range): + upper = literal.min_elem() + elif isinstance(literal, Ranges): + upper = literal.min_elem() + except ParameterOperableHasNoLiteral: + upper = float("inf") * HasUnit.get_units(value) + + return Ranges(Range(lower, upper)) + + def assert_any_predicate[ArgType]( + self, + predicates: list["Solver.PredicateWithInfo[ArgType]"], + lock: bool, + suppose_constraint: Predicate | None = None, + minimize: Expression | None = None, + ) -> Solver.SolveResultAny[ArgType]: raise NotImplementedError() diff --git a/src/faebryk/core/parameter.py b/src/faebryk/core/parameter.py index 2cb72123..e0e45d35 100644 --- a/src/faebryk/core/parameter.py +++ b/src/faebryk/core/parameter.py @@ -51,18 +51,11 @@ class ParameterOperatable(Node): operated_on: GraphInterface @property - def domain(self) -> "Domain": - raise NotImplementedError() - - def get_operations(self) -> set["Expression"]: - res = self.operated_on.get_connected_nodes(types=[Expression]) - return cast(set[Expression], res) + def domain(self) -> "Domain": ... - def has_implicit_constraint(self) -> bool: - raise NotImplementedError() + def has_implicit_constraint(self) -> bool: ... - def has_implicit_constraints_recursive(self) -> bool: - raise NotImplementedError() + def has_implicit_constraints_recursive(self) -> bool: ... @staticmethod def sort_by_depth( @@ -354,25 +347,32 @@ def if_then_else( # ) -> None: ... # ---------------------------------------------------------------------------------- - - def get_operators[T: "Expression"](self, types: type[T] | None = None) -> list[T]: + def get_operations[T: "Expression"](self, types: type[T] | None = None) -> set[T]: if types is None: types = Expression # type: ignore types = cast(type[T], types) assert issubclass(types, Expression) - return cast(list[T], self.operated_on.get_connected_nodes(types=[types])) + return cast(set[T], self.operated_on.get_connected_nodes(types=[types])) - def get_literal(self) -> Literal: - iss = self.get_operators(Is) + def get_literal(self, op: type["Expression"] | None = None) -> Literal: + if op is None: + op = Is + iss = self.get_operations(op) try: literal_is = find(o for i in iss for o in i.get_literal_operands()) except KeyErrorNotFound as e: raise ParameterOperableHasNoLiteral( - self, f"Parameter {self} has no literal" + self, f"Parameter {self} has no literal for op {op}" ) from e return literal_is + # type checks + + @staticmethod + def is_number_literal(value: Any) -> bool: + return isinstance(value, (int, float, Unit, Quantity)) + def has_implicit_constraints_recursive(po: ParameterOperatable.All) -> bool: if isinstance(po, ParameterOperatable): diff --git a/src/faebryk/library/_F.py b/src/faebryk/library/_F.py index 484aca7d..92be19b5 100644 --- a/src/faebryk/library/_F.py +++ b/src/faebryk/library/_F.py @@ -29,6 +29,7 @@ from faebryk.library.has_overriden_name import has_overriden_name from faebryk.library.has_linked_pad import has_linked_pad from faebryk.library.has_reference import has_reference +from faebryk.library.has_footprint_requirement import has_footprint_requirement from faebryk.library.can_bridge import can_bridge from faebryk.library.can_specialize import can_specialize from faebryk.library.has_descriptive_properties import has_descriptive_properties @@ -37,7 +38,6 @@ from faebryk.library.has_simple_value_representation import has_simple_value_representation from faebryk.library.has_capacitance import has_capacitance from faebryk.library.has_construction_dependency import has_construction_dependency -from faebryk.library.has_footprint_requirement import has_footprint_requirement from faebryk.library.has_kicad_ref import has_kicad_ref from faebryk.library.has_picker import has_picker from faebryk.library.has_pcb_layout import has_pcb_layout @@ -61,6 +61,7 @@ from faebryk.library.has_overriden_name_defined import has_overriden_name_defined from faebryk.library.has_linked_pad_defined import has_linked_pad_defined from faebryk.library.Symbol import Symbol +from faebryk.library.has_footprint_requirement_defined import has_footprint_requirement_defined from faebryk.library.can_bridge_defined import can_bridge_defined from faebryk.library.has_descriptive_properties_defined import has_descriptive_properties_defined from faebryk.library.has_datasheet_defined import has_datasheet_defined @@ -68,7 +69,6 @@ from faebryk.library.has_simple_value_representation_based_on_params import has_simple_value_representation_based_on_params from faebryk.library.has_simple_value_representation_based_on_params_chain import has_simple_value_representation_based_on_params_chain from faebryk.library.has_simple_value_representation_defined import has_simple_value_representation_defined -from faebryk.library.has_footprint_requirement_defined import has_footprint_requirement_defined from faebryk.library.has_multi_picker import has_multi_picker from faebryk.library.has_pcb_layout_defined import has_pcb_layout_defined from faebryk.library.has_single_connection_impl import has_single_connection_impl diff --git a/src/faebryk/library/has_simple_value_representation_based_on_params_chain.py b/src/faebryk/library/has_simple_value_representation_based_on_params_chain.py index 3c84cd5f..ea14d3c7 100644 --- a/src/faebryk/library/has_simple_value_representation_based_on_params_chain.py +++ b/src/faebryk/library/has_simple_value_representation_based_on_params_chain.py @@ -69,7 +69,7 @@ def _get_value(self) -> str: if isinstance(domain, Numbers): unit = self.unit if self.unit is not None else self.param.units - if isinstance(value, (int, float, Unit, Quantity)): + if Parameter.is_number_literal(value): # TODO If tolerance, maybe hint that it's weird there isn't any return to_si_str(value, unit, 2) if isinstance(value, P_Set): diff --git a/test/core/test_parameters.py b/test/core/test_parameters.py index acf9a927..773ec6d1 100644 --- a/test/core/test_parameters.py +++ b/test/core/test_parameters.py @@ -10,7 +10,7 @@ from faebryk.core.defaultsolver import DefaultSolver from faebryk.core.module import Module from faebryk.core.node import Node -from faebryk.core.parameter import And, Or, Parameter +from faebryk.core.parameter import And, Parameter from faebryk.libs.library import L from faebryk.libs.logging import setup_basic_logging from faebryk.libs.sets import Range, Ranges