Skip to content

Return NotImplemented for Expr and GenExpr operators to simplify#1182

Open
Zeroto521 wants to merge 63 commits intoscipopt:masterfrom
Zeroto521:expr/notimplemented
Open

Return NotImplemented for Expr and GenExpr operators to simplify#1182
Zeroto521 wants to merge 63 commits intoscipopt:masterfrom
Zeroto521:expr/notimplemented

Conversation

@Zeroto521
Copy link
Copy Markdown
Contributor

Python prefers to use NotImplemented to handle the unknown input types. It's clear.

Here is an example.
A.__add__ can only receive int type. But a + b can work. When Python calls A.__add__(B), it returns NotImplemented. Then Python will call B.__radd__(A). If both of them are NotImplemented, Python will raise a TypeError.

So we use NotImplemented to simplify Expr, GenExpr, and MatrixExpr.

from __future__ import annotations


class A:
    def __init__(self, value: int):
        self.value = value

    def __add__(self, other: int) -> int:
        if not isinstance(other, int):
            return NotImplemented
        return self.value + other


class B:
    def __init__(self, value: int):
        self.value = value

    def __add__(self, other: A) -> B:
        if not isinstance(other, A):
            return NotImplemented
        return self.value + other.value

    def __radd__(self, other: A) -> int:
        if not isinstance(other, A):
            return NotImplemented
        return self.value + other.value


if __name__ == "__main__":
    a = A(5)
    b = B(10)

    print(a + 3)  # print 8
    print(a + b)  # print 15
    print(a + "test")  # raises TypeError
# Traceback (most recent call last):
#   File "test.py", line 35, in <module>
#     print(a + "test")  # raises TypeError
#           ~~^~~~~~~~
# TypeError: unsupported operand type(s) for +: 'A' and 'str'

Zeroto521 and others added 19 commits January 22, 2026 13:09
Improves the _expr_richcmp function by using explicit type checks, handling numpy arrays and numbers more robustly, and leveraging Python C API comparison constants. This refactor enhances error handling and code readability, and ensures unsupported types raise clear exceptions.
Replaces repeated isinstance checks for numeric types with a shared NUMBER_TYPES tuple. This improves maintainability and consistency in type checking within _expr_richcmp and related code.
Modified __mul__ and __add__ methods in Expr and GenExpr classes to return NotImplemented when the operand is not an instance of EXPR_OP_TYPES, improving type safety and operator behavior.
Replaces the explicit type tuple with EXPR_OP_TYPES in the type check for 'other' in _expr_richcmp, raising TypeError for unsupported types. This improves maintainability and consistency in type validation.
Enhanced type validation in expression operator overloads by returning NotImplemented for unsupported types and raising TypeError in buildGenExprObj for invalid inputs. Removed special handling for numpy arrays and simplified code paths for better maintainability and clearer error reporting.
Simplified the implementation of __truediv__ and __rtruediv__ by directly using the division operator instead of calling __truediv__ explicitly on generated expression objects.
Replaces manual timing with the timeit module for more accurate performance measurement in matrix operation tests. Updates assertions to require the optimized implementation to be at least 25% faster, and reduces test parameterization to n=100 for consistency.
Replaces random matrix generation with a stacked matrix of zeros and ones in test_matrix_dot_performance to provide more controlled test data.
Replaces the custom _is_number function with isinstance checks against NUMBER_TYPES for improved clarity and consistency in type checking throughout expression operations.
Simplifies type checking and arithmetic operations in Expr and GenExpr classes by removing unnecessary float conversions and reordering type checks. Also improves error handling for exponentiation and constraint comparisons.
Documented that Expr and GenExpr now return NotImplemented when they cannot handle other types in calculations.
Replaces EXPR_OP_TYPES with GENEXPR_OP_TYPES in GenExpr and related functions to distinguish between Expr and GenExpr operations. Removes special handling for GenExpr in Expr arithmetic methods, simplifying type logic and improving consistency.
Clarified the changelog note to specify that NotImplemented is returned for Expr and GenExpr operators when they can't handle input types in calculations.
Corrects error messages in Expr and GenExpr exponentiation to display the correct variable. Removes an unnecessary assertion in Model and replaces a call to _is_number with isinstance for type checking in readStatistics.
Replaces EXPR_OP_TYPES with GENEXPR_OP_TYPES in the type check to ensure correct type validation in the _expr_richcmp method.
Comment thread src/pyscipopt/expr.pxi
Comment thread src/pyscipopt/expr.pxi Outdated
Casts the exponent base to float in GenExpr's __pow__ method to prevent type errors when reformulating expressions using log and exp.
@Zeroto521 Zeroto521 changed the title Return NotImplemented for Expr and GenExpr operators Return NotImplemented for Expr and GenExpr operators to simplify Jan 31, 2026
Explicitly converts 'other' to float in the exponentiation method to avoid type errors when using non-float numeric types.
The _is_number symbol was removed from the list of incomplete stubs in scip.pyi, likely because it is no longer needed or has been implemented.
Ensures that multiplication of Expr by numeric types consistently uses float conversion, preventing potential type errors when multiplying terms.
Copilot AI review requested due to automatic review settings February 1, 2026 02:28
@Joao-Dionisio Joao-Dionisio enabled auto-merge (squash) April 3, 2026 14:20
Comment thread tests/test_expr.py Outdated
Copy link
Copy Markdown
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

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


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

Comment thread src/pyscipopt/expr.pxi
Comment thread src/pyscipopt/scip.pxi Outdated
Comment thread src/pyscipopt/scip.pxi Outdated
Comment thread src/pyscipopt/expr.pxi Outdated
Comment thread tests/test_expr.py
@Joao-Dionisio
Copy link
Copy Markdown
Member

After addressing Copilot's comments, this can be merged.

Modify tests/test_expr.py to better cover comparison directions: change a genexpr >= '1' assertion to '1' >= genexpr to test the reflected comparison, and add a new assertion that genexpr <= '1' raises TypeError. Ensures both operand orders for <=/>= with a genexpr and a string are validated to raise TypeError.
Replace the previous inline PyNumber_Check heuristic with explicit runtime type inspection: obtain the object's Py_TYPE, treat builtin int/float as numbers, explicitly reject numpy arrays, Expr/GenExpr and Python lists/tuples, and fall back to PyNumber_Check otherwise. Also remove the inline qualifier. This tightens number detection to avoid misclassifying arrays or expression objects as numeric values.
In src/pyscipopt/expr.pxi, replace the explicit Cython cast `1.0 / <double>other * self` with `1.0 / other * self`. This removes the redundant C-level cast when dividing by a numeric Python object, simplifying the code and avoiding potential type/casting issues while preserving the intended behavior.
auto-merge was automatically disabled April 5, 2026 02:07

Head branch was pushed to by a user without write access

Add an explicit type check in Model to ensure the provided expr is either an Expr or a numeric value before coercing it via Expr() + expr. If the value is neither, raise a TypeError with an informative message. This prevents silent or invalid coercions and improves error diagnostics when callers pass unsupported types.
Replace a bare except in src/pyscipopt/scip.pxi (inside readStatistics) with except (ValueError, TypeError) to avoid unintentionally catching unrelated exceptions (e.g. KeyboardInterrupt/SystemExit) while still handling float conversion failures for statistic values.
@Zeroto521 Zeroto521 requested a review from Joao-Dionisio April 5, 2026 02:30
Replace Python-level type comparisons with C API checks: use PyLong_Check and PyFloat_Check to detect ints/floats instead of comparing Py_TYPE(o) to int/float. Also switch the numpy cimport to 'cimport numpy as cnp' and use cnp.PyArray_Check. Add the necessary cimports for PyFloat_Check and PyLong_Check and simplify the _is_number implementation for correctness and performance.
Generalize expression-type handling by replacing explicit (Expr, GenExpr) checks with the common ExprLike base in _is_number, ensuring all expression-like objects are treated consistently. Also update the _expr_richcmp function signature to accept ExprLike self for proper typing in Cython. Minor whitespace cleanup in the Expr class definition.
Add the 'inline' qualifier to the Cython helper _is_number in src/pyscipopt/expr.pxi to allow the compiler to inline it and reduce call overhead for frequent numeric/type checks. No behavioral changes intended.
Comment thread tests/test_expr.py
@Joao-Dionisio
Copy link
Copy Markdown
Member

@Zeroto521 can you please resolve the conflicts so I merge this?

@Zeroto521
Copy link
Copy Markdown
Contributor Author

@Zeroto521 can you please resolve the conflicts so I merge this?

@Joao-Dionisio, it will raise an error if I merge the master branch directly first. See details here: #1182 (comment)

@Joao-Dionisio
Copy link
Copy Markdown
Member

#1203 has been merged.

Update test assertions to match the new ordering of arguments in the string representation for np.power. The product argument order now places log(2.0) before the sum(...) term, so assertions for np.power(2, x) and np.power(a, x) were updated accordingly.
Replace strict type(a) is np.ndarray with isinstance(a, np.ndarray) in ExprLike.__call__ (src/pyscipopt/expr.pxi). This allows numpy ndarray subclasses (e.g., masked arrays or custom ndarray types) to be recognized as arrays while preserving the existing dtype kind validation and overall behavior.
Convert np.generic arguments to native Python types (using .item()) before ufunc dispatch in ExprLike. This prevents recursive __array_ufunc__ calls between np.generic and MatrixExpr (e.g., np.generic + MatrixExpr), ensuring the operation dispatches correctly.
Comment thread CHANGELOG.md
### Added
### Fixed
### Changed
- Return NotImplemented for `Expr` and `GenExpr` operators, if they can't handle input types in the calculation
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should this PR be in 6.2.0 or an unreleased version?

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.

3 participants