@@ -138,6 +138,11 @@ def _to_sympy_expr(self, ast):
138138 try :
139139 return sp .Number (float (ast .value )) if '.' in ast .value else sp .Number (int (ast .value ))
140140 except ValueError :
141+ # Map booleans
142+ if ast .value .lower () == 'true' :
143+ return sp .true
144+ if ast .value .lower () == 'false' :
145+ return sp .false
141146 return sp .Symbol (ast .value .replace ('.' , '_' ))
142147
143148 # Handle indexed attributes: a[b].c
@@ -157,16 +162,37 @@ def _to_sympy_expr(self, ast):
157162
158163 args = [self ._to_sympy_expr (child ) for child in ast .children ]
159164
165+
166+ # Normalize ==/!= with boolean literals to X / !X
167+ if ast .value in ('==' , '!=' ) and len (args ) == 2 :
168+ L , R = args
169+ is_L_bool = L is sp .true or L is sp .false
170+ is_R_bool = R is sp .true or R is sp .false
171+ if is_L_bool or is_R_bool :
172+ bval = True if (L is sp .true or R is sp .true ) else False
173+ expr = R if is_L_bool else L
174+ if ast .value == '==' :
175+ return expr if bval else sp .Not (expr )
176+ else : # '!='
177+ return sp .Not (expr ) if bval else expr
178+
160179 if ast .value in ('&&' , '||' , '!' , '==' , '!=' , '>' , '<' , '>=' , '<=' ):
161180 return getattr (sp , self ._sympy_operator (ast .value ))(* args )
162181 elif ast .value == '/' :
163182 return sp .Mul (args [0 ], sp .Pow (args [1 ], - 1 ))
164183 # # Failed Use sympy.floor to correctly model Solidity's integer division
165184 # return sp.floor(args[0] / args[1])
166185 elif ast .value == '+' :
167- return sp .Add (* args )
186+ # unary plus: +x → x ; n-ary Add otherwise
187+ return args [0 ] if len (args ) == 1 else sp .Add (* args )
168188 elif ast .value == '-' :
169- return sp .Add (args [0 ], - args [1 ])
189+ # Support unary negation and binary subtraction
190+ if len (args ) == 1 :
191+ return sp .Mul (sp .Integer (- 1 ), args [0 ])
192+ elif len (args ) == 2 :
193+ return sp .Add (args [0 ], sp .Mul (sp .Integer (- 1 ), args [1 ]))
194+ else :
195+ raise ValueError (f"Invalid number of children for '-' node: { len (args )} " )
170196 elif ast .value == '*' :
171197 return sp .Mul (* args )
172198 elif '()' in ast .value :
@@ -363,15 +389,15 @@ def _implies(self, expr1, expr2, level=0):
363389 if isinstance (expr1 , relational_operators ) and isinstance (expr2 , relational_operators ):
364390 printer (f'In relational base cases; expr1: { expr1 } , expr2: { expr2 } ' , level )
365391
366- # Z3+nonneg fast-path before the other branches
367- # Prefer Z3 under non-negative domain if there are variables
368- try :
369- if expr1 .free_symbols or expr2 .free_symbols :
370- z3_result = self ._z3_implies_with_nonneg (expr1 , expr2 )
371- printer (f"Z3 (nonneg) implication { expr1 } -> { expr2 } : { z3_result } " , level )
372- return z3_result
373- except Exception as e :
374- printer (f"Error (Z3 nonneg implication): { e } " , level )
392+ # # Z3+nonneg fast-path before the other branches
393+ # # Prefer Z3 under non-negative domain if there are variables
394+ # try:
395+ # if expr1.free_symbols or expr2.free_symbols:
396+ # z3_result = self._z3_implies_with_nonneg(expr1, expr2)
397+ # printer(f"Z3 (nonneg) implication {expr1} -> {expr2}: {z3_result}", level)
398+ # return z3_result
399+ # except Exception as e:
400+ # printer(f"Error (Z3 nonneg implication): {e}", level)
375401
376402 # Check for Eq vs non-Eq comparisons; we don't handle this well, let's return False
377403 if (isinstance (expr1 , sp .Eq ) and not isinstance (expr2 , sp .Eq )) or (not isinstance (expr1 , sp .Eq ) and isinstance (expr2 , sp .Eq )):
@@ -455,38 +481,51 @@ def _implies(self, expr1, expr2, level=0):
455481 printer (f'type of expr2.rhs: { type (expr2 .rhs )} ' )
456482
457483
484+ # Detect ANY non-trivial numeric scaling (not just fractions) anywhere:
485+ # - Multiplicative numeric factor != ±1 (e.g., *2, *1.5, *1e18, *1/2)
486+ # - Negative powers (division), e.g., 2**-1
487+ def _has_numeric_scale (e ) -> bool :
488+ try :
489+ # Direct negative power (e.g., 2**-1) => division
490+ if isinstance (e , sp .Pow ) and e .exp .is_number and e .exp < 0 :
491+ return True
492+ # Multiplicative factors with a numeric coefficient or negative powers
493+ if isinstance (e , sp .Mul ):
494+ for aa in e .args :
495+ if isinstance (aa , sp .Pow ) and aa .exp .is_number and aa .exp < 0 :
496+ return True
497+ # Any numeric factor not ±1 (covers integers, floats, rationals)
498+ if isinstance (aa , sp .Number ) and aa not in (1 , - 1 ):
499+ return True
500+ # Recurse within Mul arguments
501+ return any (_has_numeric_scale (aa ) for aa in e .args )
502+ # Recurse through additive structures without flagging bare integers
503+ if isinstance (e , sp .Add ):
504+ return any (_has_numeric_scale (aa ) for aa in e .args )
505+ except Exception :
506+ pass
507+ return False
458508
459- # even if one of the above lhs and rhs's is sympy.core.mul.Mul and then one of its args is a number or a float bigger or lower than 1, we should switch to z3; we are not handling "1" case since it is working with sympy already, don't want to break a working prototype
460- if any (isinstance (arg , sp .Mul ) and any (isinstance (a , (sp .Number , sp .Float )) and (a > 1 or a < 1 ) for a in arg .args ) for arg in [expr1 .lhs , expr1 .rhs , expr2 .lhs , expr2 .rhs ]):
461- printer (f'One of the arguments is a Mul, switching to z3 ...' , level )
462-
509+ if any (_has_numeric_scale (arg ) for arg in [expr1 .lhs , expr1 .rhs , expr2 .lhs , expr2 .rhs ]):
510+ printer (f'Numeric scaling detected; switching to z3 with non-negativity.' , level )
463511
464512 z3_expr1 = self .sympy_to_z3 (expr1 )
465513 z3_expr2 = self .sympy_to_z3 (expr2 )
466514
467515 variables = {str (sym ) for sym in expr1 .free_symbols .union (expr2 .free_symbols )}
468516 z3_vars = {var : z3 .Real (var ) for var in variables } # Convert to Z3 Reals
469517
470-
471-
472518 solver = z3 .Solver ()
473-
474- # Add constraints to ensure all variables are greater than 0
519+ # Solidity-like domains for this numeric monotonicity reasoning
475520 for var in z3_vars .values ():
476- solver .add (var >= 0 )
521+ solver .add (var >= 0 )
477522
523+ # Check UNSAT of expr1 ∧ ¬expr2
478524 solver .add (z3_expr1 , z3 .Not (z3_expr2 ))
479-
480-
481-
482-
483- # Check satisfiability
484525 if solver .check () == z3 .sat :
485- # If satisfiable, implication does not hold
486526 printer (f"Implies { expr1 } to { expr2 } : False" , level = 0 )
487527 return False
488528 else :
489- # If unsatisfiable, implication holds
490529 printer (f"Implies { expr1 } to { expr2 } : True" , level = 0 )
491530 return True
492531 else :
0 commit comments