3636 PolynomialMapEvaluation ,
3737 PolynomialMapInverseSynthesisRequest ,
3838 PolynomialMapInverseVerifyRequest ,
39+ PolynomialVariable ,
3940 RationalPolynomialMap ,
4041 RationalPolynomialPoint ,
4142 RationalPolynomialTerm ,
5758
5859_INVERSE_SOLVER_SHUTDOWN_TIMEOUT_SECONDS = 1.0
5960_MAX_CANONICALIZATION_COEFFICIENT_DIGITS = 256
61+ _MAX_CANONICALIZATION_DUPLICATE_TERMS = 64
6062
6163
6264class _SparsePolynomialInputTerm (ContractModel ):
@@ -89,12 +91,40 @@ class _SparsePolynomialInput(ContractModel):
8991 )
9092
9193
94+ class _PolynomialMapInverseVerifyInput (ContractModel ):
95+ """Inverse-check request shape without composition operation budgets."""
96+
97+ forward_map : RationalPolynomialMap
98+ inverse_map : RationalPolynomialMap
99+ source_variables : tuple [PolynomialVariable , ...] = Field (min_length = 1 , max_length = 4 )
100+ target_variables : tuple [PolynomialVariable , ...] = Field (min_length = 1 , max_length = 4 )
101+
102+ @model_validator (mode = "after" )
103+ def require_compatible_ordered_rings (self ) -> Self :
104+ if self .forward_map .variables != self .source_variables :
105+ raise ValueError ("forward map variables must equal source_variables" )
106+ if self .inverse_map .variables != self .target_variables :
107+ raise ValueError ("inverse map variables must equal target_variables" )
108+ if len (self .source_variables ) != len (self .target_variables ):
109+ raise ValueError ("source and target dimensions must agree" )
110+ if len (self .forward_map .coordinates ) != len (self .target_variables ):
111+ raise ValueError ("forward map coordinate count must match target_variables" )
112+ if len (self .inverse_map .coordinates ) != len (self .source_variables ):
113+ raise ValueError ("inverse map coordinate count must match source_variables" )
114+ return self
115+
116+
92117def _require_bounded_duplicate_accumulation (
93118 parsed : _SparsePolynomialInput ,
94119) -> None :
95120 counts : dict [tuple [int , ...], int ] = {}
96121 for term in parsed .terms :
97122 counts [term .exponents ] = counts .get (term .exponents , 0 ) + 1
123+ if any (count > _MAX_CANONICALIZATION_DUPLICATE_TERMS for count in counts .values ()):
124+ raise ValueError (
125+ "duplicate-term canonicalization exceeds the "
126+ f"{ _MAX_CANONICALIZATION_DUPLICATE_TERMS } -term accumulation budget"
127+ )
98128 for term in parsed .terms :
99129 if counts [term .exponents ] > 1 :
100130 require_bounded_rational (
@@ -107,7 +137,7 @@ def _require_bounded_duplicate_accumulation(
107137def _structural_sparse_polynomial (
108138 value : object ,
109139) -> SparseRationalPolynomial :
110- """Build a cheap canonical support representative without coefficient sums ."""
140+ """Build a cheap shape representative without applying operation budgets ."""
111141
112142 parsed = _SparsePolynomialInput .model_validate (value )
113143 _require_bounded_duplicate_accumulation (parsed )
@@ -601,11 +631,12 @@ def _validate_request[RequestModel: ContractModel](
601631 error_factory : Callable [[str , str , str ], CapabilityInvocationError ] | None = None ,
602632) -> RequestModel :
603633 try :
604- structural_payload = _normalize_sparse_polynomial_inputs (
605- payload ,
606- normalize = _structural_sparse_polynomial ,
607- )
608- model .model_validate (structural_payload )
634+ if model is PolynomialMapInverseVerifyRequest :
635+ structural_payload = _normalize_sparse_polynomial_inputs (
636+ payload ,
637+ normalize = _structural_sparse_polynomial ,
638+ )
639+ _PolynomialMapInverseVerifyInput .model_validate (structural_payload )
609640 canonical_payload = _normalize_sparse_polynomial_inputs (
610641 payload ,
611642 normalize = _canonical_sparse_polynomial ,
0 commit comments