Skip to content

Conversation

@Zeroto521
Copy link
Contributor

to fix #1062

Introduces the test_evaluate function to verify correct evaluation of various expressions using Model.getVal, including arithmetic and trigonometric operations, and checks for TypeError on invalid input.
Introduced _evaluate methods to Term, Expr, and all GenExpr subclasses for consistent evaluation of expressions and variables. Refactored Solution and Model to use these methods, simplifying and unifying value retrieval for variables and expressions. Cleaned up class definitions and improved hashing and equality for Term and Variable.
Replaced explicit loop with flat iterator for evaluating matrix expressions in Solution.getVal, improving performance and code clarity.
Introduces a new test 'test_evaluate' to verify correct evaluation of matrix variable division and summation in the model.
Eliminated special handling for MatrixExpr and MatrixGenExpr in Solution.__getitem__, simplifying the method to only evaluate single expressions. This change streamlines the code and removes unnecessary numpy array construction.
Renamed 'vars' to 'vartuple' and '_hash' to 'hashval' in the Term class for clarity. Updated all references and methods to use the new attribute names, improving code readability and consistency.
Changed internal evaluation methods in expression classes from float to double for improved numerical precision. Updated type declarations and imports accordingly.
Added type annotations to the Term constructor and specified types for class attributes. This improves code clarity and type safety.
Changed all _evaluate methods in expression classes from cdef to cpdef to make them accessible from Python. Moved the definition of terms and _evaluate to the Expr class in scip.pxd, and refactored Variable to inherit from Expr in scip.pxd, consolidating class member definitions. These changes improve the interface and maintainability of expression evaluation.
Ensures that MatrixExpr._evaluate returns a numpy ndarray when appropriate. Adds a test to verify the return type of getVal for matrix variables.
Deleted the _evaluate method from the Solution class in the scip.pyi stub file, as it is no longer needed. Also added TYPE_CHECKING to the typing imports.
Applied the @disjoint_base decorator to the Term and UnaryExpr classes in scip.pyi to clarify their base class relationships. This may improve type checking or class hierarchy handling.
Appended '# noqa: F401' to the 'ClassVar' import to suppress linter warnings about unused imports in scip.pyi.
Replaces the @np.vectorize-decorated _evaluate function with an np.frompyfunc-based implementation for evaluating expressions with solutions. This change may improve compatibility and performance when applying _evaluate to arrays.
Refactored the _evaluate method in MatrixExpr to always return the result as a NumPy ndarray, removing the conditional type check.
@codecov
Copy link

codecov bot commented Jan 9, 2026

Codecov Report

❌ Patch coverage is 62.79070% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.12%. Comparing base (5ceac82) to head (7cd196a).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
src/pyscipopt/expr.pxi 62.50% 12 Missing ⚠️
src/pyscipopt/matrix.pxi 25.00% 3 Missing ⚠️
src/pyscipopt/scip.pxi 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1148      +/-   ##
==========================================
+ Coverage   55.07%   55.12%   +0.04%     
==========================================
  Files          24       24              
  Lines        5420     5448      +28     
==========================================
+ Hits         2985     3003      +18     
- Misses       2435     2445      +10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Added a second variable 'y' to test_evaluate and included new assertions for expressions involving both variables. This improves coverage of expression evaluation, including addition and division of variables.
Changed the expected result of m.getVal(x + y + 1) from 3 to 4 in test_evaluate to reflect updated logic or correct the test expectation.
Replaces the standard Python dict iteration in Expr._evaluate with a more efficient C-level iteration using PyDict_Next. This change improves performance by avoiding Python-level overhead when evaluating expressions.
Renamed local variables 'scip' and 'sol' to 'scip_ptr' and 'sol_ptr' in the Term class to improve clarity and avoid shadowing the input parameter 'sol'.
Updated variable initialization in Term._evaluate and Expr._evaluate for clarity and consistency. Changed res initialization to 1.0 and consolidated variable declarations to improve code readability.
Appended 'except *' to all cpdef double _evaluate methods in expression classes to ensure proper exception handling in Cython. This change improves error propagation and consistency across the codebase.
Corrects the _evaluate method implementations in VarExpr, PowExpr, and UnaryExpr to explicitly cast children[0] to GenExpr before calling _evaluate. This ensures proper method resolution and avoids potential attribute errors.
Refactored the _evaluate methods in SumExpr and ProdExpr to use indexed loops for better performance and type safety. Added early exit in ProdExpr when result becomes zero to improve efficiency.
Corrects the _evaluate method in UnaryExpr to properly evaluate the child expression before applying the math operation. This ensures that the math function receives a numeric value instead of an expression object.
@Zeroto521
Copy link
Contributor Author

Zeroto521 commented Jan 19, 2026

Use Cython code style to speed up _evaluate. For example, use PyDict_Next to iterate over dict, which could avoid creating temporary Python objects.

@Joao-Dionisio
Copy link
Member

Joao-Dionisio commented Jan 20, 2026

This is what another AI had to say about the PR, can you please address or refute the comments?

  Issues/Concerns                                                                                                                                                                                               
                                                                                                                                                                                                                
  1. Hash function change (potential breaking change):                                                                                                                                                          
  # Before:                                                                                                                                                                                                     
  self.hashval = sum(self.ptrtuple)                                                                                                                                                                             
  # After:                                                                                                                                                                                                      
  self.hashval = hash(self.ptrtuple)                                                                                                                                                                            
  1. While hash(self.ptrtuple) is arguably better (avoids collisions for terms with same sum of pointers), this changes the hash values of existing Term objects. Any code that pickled/stored hash values would
   break. This should be documented in CHANGELOG as a breaking change or reverted if backward compatibility is needed.                                                                                          
  2. Missing _evaluate declaration in scip.pxd for GenExpr and its subclasses. Only Expr._evaluate is declared:                                                                                                 
  cdef class Expr:                                                                                                                                                                                              
      cdef public terms                                                                                                                                                                                         
      cpdef double _evaluate(self, Solution sol)                                                                                                                                                                
  2. The GenExpr classes should also have their _evaluate declared in .pxd for type safety.                                                                                                                     
  3. Module-level _evaluate in matrix.pxi:                                                                                                                                                                      
  _evaluate = np.frompyfunc(lambda expr, sol: expr._evaluate(sol), 2, 1)                                                                                                                                        
  3. This creates a module-level variable that shadows the method name. Consider renaming to _matrix_evaluate or similar.                                                                                                                                                                                                                                           
  4. Type annotation change in getSolVal:                                                                                                                                                                       
  def getSolVal(self, Solution sol, expr: Union[Expr, GenExpr]) -> float:                                                                                                                                       
  4. The return type is float, but for MatrixExpr it returns NDArray. This should be Union[float, NDArray] or keep it untyped.                                                                                  
  5. The if TYPE_CHECKING: double = float pattern is unconventional. It's used to avoid IDE warnings but could confuse readers.                                                                                 
                                                                                                                                                                                                                
  Minor Suggestions                                                                                                                                                                                             
                                                                                                                                                                                                                
  1. The @disjoint_base decorator added to Term and UnaryExpr in stubs - what's the purpose? This should be documented.                                                                                         
  2. Consider adding a docstring to the _evaluate methods explaining what they do.                                                                                                                              

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes issue #1062 by adding support for evaluating GenExpr (general expression) types when calling Model.getVal() or Model.getSolVal(). Previously, only Expr (polynomial expression) types were supported, which caused TypeError when trying to evaluate expressions containing division operations or other non-polynomial operations.

Changes:

  • Added _evaluate() methods to all GenExpr subclasses (SumExpr, ProdExpr, VarExpr, PowExpr, UnaryExpr, Constant) and Expr/Term classes to support expression evaluation
  • Refactored Solution.__getitem__ to delegate evaluation to expression objects via their _evaluate() methods
  • Updated Model.getSolVal() to accept both Expr and GenExpr types

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/pyscipopt/expr.pxi Added _evaluate() implementation for Term, Expr, and all GenExpr subclasses; converted Term from Python class to Cython cdef class; improved Term hash function
src/pyscipopt/scip.pxi Simplified Solution.__getitem__ to call expr._evaluate(); updated Model.getSolVal() to accept GenExpr types
src/pyscipopt/scip.pxd Added cpdef _evaluate declaration to Expr class
src/pyscipopt/scip.pyi Added @disjoint_base decorators to Term and UnaryExpr; removed obsolete _evaluate method from Solution stub
src/pyscipopt/matrix.pxi Added _evaluate() method to MatrixExpr using numpy universal function
tests/test_expr.py Added comprehensive tests for expression evaluation including division, power, and trigonometric functions
tests/test_matrix_variable.py Added basic test for matrix variable evaluation
CHANGELOG.md Documented the fix for GenExpr support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Zeroto521 and others added 10 commits January 21, 2026 13:11
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Removed the unused _evaluate function and replaced its usage with _vec_evaluate in the MatrixExpr class. This change clarifies the evaluation logic and ensures consistency in function naming.
Updated the Term class in expr.pxi to use Py_ssize_t for the hashval attribute instead of int, ensuring compatibility with Python's hash function return type and improving type safety.
Special-cases the 'abs' operator in UnaryExpr._evaluate to use math.fabs, ensuring correct evaluation for absolute value expressions.
Modified the getSolVal method in the Model class to return either a float or a numpy NDArray of float64, reflecting support for vectorized solution values. Also imported NDArray from numpy.typing to enable this type annotation.
Updated the getVal method in the Model class to accept GenExpr in addition to Expr and MatrixExpr. This broadens the method's applicability to more expression types.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The include statement for matrix.pxi was removed from expr.pxi as it is no longer needed.
@Zeroto521
Copy link
Contributor Author

Except for hash(self.ptrtuple) (#1148 (comment)), others are fixed.

This is what another AI had to say about the PR, can you please address or refute the comments?

  Issues/Concerns                                                                                                                                                                                               
                                                                                                                                                                                                                
  1. Hash function change (potential breaking change):                                                                                                                                                          
  # Before:                                                                                                                                                                                                     
  self.hashval = sum(self.ptrtuple)                                                                                                                                                                             
  # After:                                                                                                                                                                                                      
  self.hashval = hash(self.ptrtuple)                                                                                                                                                                            
  1. While hash(self.ptrtuple) is arguably better (avoids collisions for terms with same sum of pointers), this changes the hash values of existing Term objects. Any code that pickled/stored hash values would
   break. This should be documented in CHANGELOG as a breaking change or reverted if backward compatibility is needed.                                                                                          
  2. Missing _evaluate declaration in scip.pxd for GenExpr and its subclasses. Only Expr._evaluate is declared:                                                                                                 
  cdef class Expr:                                                                                                                                                                                              
      cdef public terms                                                                                                                                                                                         
      cpdef double _evaluate(self, Solution sol)                                                                                                                                                                
  2. The GenExpr classes should also have their _evaluate declared in .pxd for type safety.                                                                                                                     
  3. Module-level _evaluate in matrix.pxi:                                                                                                                                                                      
  _evaluate = np.frompyfunc(lambda expr, sol: expr._evaluate(sol), 2, 1)                                                                                                                                        
  3. This creates a module-level variable that shadows the method name. Consider renaming to _matrix_evaluate or similar.                                                                                                                                                                                                                                           
  4. Type annotation change in getSolVal:                                                                                                                                                                       
  def getSolVal(self, Solution sol, expr: Union[Expr, GenExpr]) -> float:                                                                                                                                       
  4. The return type is float, but for MatrixExpr it returns NDArray. This should be Union[float, NDArray] or keep it untyped.                                                                                  
  5. The if TYPE_CHECKING: double = float pattern is unconventional. It's used to avoid IDE warnings but could confuse readers.                                                                                 
                                                                                                                                                                                                                
  Minor Suggestions                                                                                                                                                                                             
                                                                                                                                                                                                                
  1. The @disjoint_base decorator added to Term and UnaryExpr in stubs - what's the purpose? This should be documented.                                                                                         
  2. Consider adding a docstring to the _evaluate methods explaining what they do.                                                                                                                              

@Joao-Dionisio Joao-Dionisio enabled auto-merge (squash) January 21, 2026 10:39
@Joao-Dionisio Joao-Dionisio merged commit 89d0391 into scipopt:master Jan 21, 2026
3 checks passed
@Zeroto521 Zeroto521 deleted the issue/1062 branch January 21, 2026 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: can't call getVal from a matrix expression

2 participants