# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and Engineering
# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this
# software.  This software is distributed under the 3-clause BSD License.
# ____________________________________________________________________________________
#
# Unit Tests for expression generation
#
import logging
import math
import operator

import pyomo.common.unittest as unittest

from pyomo.common.log import LoggingIntercept
from pyomo.core.expr.compare import (
    assertExpressionsEqual,
    assertExpressionsStructurallyEqual,
)
import pyomo.core.expr as EXPR
from pyomo.core.expr import (
    DivisionExpression,
    NPV_DivisionExpression,
    SumExpression,
    NPV_SumExpression,
    LinearExpression,
    MonomialTermExpression,
    NegationExpression,
    NPV_NegationExpression,
    ProductExpression,
    NPV_ProductExpression,
    PowExpression,
    NPV_PowExpression,
    AbsExpression,
    NPV_AbsExpression,
    UnaryFunctionExpression,
    NPV_UnaryFunctionExpression,
)
from pyomo.core.expr.numeric_expr import (
    ARG_TYPE,
    _categorize_arg_type,
    _known_arg_types,
    _MutableSumExpression,
    _MutableLinearExpression,
    _MutableNPVSumExpression,
    _zero_one_optimizations,
    enable_expression_optimizations,
)
from pyomo.core.expr.numvalue import NumericValue, native_types, native_numeric_types
from pyomo.core.expr.visitor import clone_expression
from pyomo.environ import ConcreteModel, Param, Var, BooleanVar
from pyomo.gdp import Disjunct

logger = logging.getLogger(__name__)


class Base:
    class SKIP:
        pass

    NUM_TESTS = 21

    def tearDown(self):
        pass

    def setUp(self):
        # Note there are 11 basic argument "types" that determine how
        # expressions are generated (defined by the _EXPR_TYPE enum):
        #
        # class _EXPR_TYPE(enum.Enum):
        #     MUTABLE = -2
        #     ASNUMERIC = -1
        #     INVALID = 0
        #     NATIVE = 1
        #     NPV = 2
        #     PARAM = 3
        #     VAR = 4
        #     MONOMIAL = 5
        #     LINEAR = 6
        #     SUM = 7
        #     OTHER = 8
        self.m = ConcreteModel()
        self.m.p0 = Param(initialize=0, mutable=False)
        self.m.p1 = Param(initialize=1, mutable=False)
        self.m.p = Param(initialize=6, mutable=False)
        self.m.q = Param(initialize=7, mutable=True)
        self.m.x = Var()
        self.m.d = Disjunct()
        self.bin = self.m.d.indicator_var.as_numeric()

        self.invalid = 'str'
        self.asbinary = self.m.d.indicator_var
        self.zero = 0
        self.one = 1
        self.native = 5
        self.npv = NPV_PowExpression((self.m.q, 2))
        self.param = self.m.p
        self.param0 = self.m.p0
        self.param1 = self.m.p1
        self.param_mut = self.m.q
        self.var = self.m.x
        self.mon_native = MonomialTermExpression((3, self.m.x))
        self.mon_param = MonomialTermExpression((self.m.q, self.m.x))
        self.mon_npv = MonomialTermExpression((self.npv, self.m.x))
        self.linear = LinearExpression([4, self.mon_native])
        self.sum = SumExpression([4, self.mon_native, self.m.x**2])
        self.other = PowExpression((self.m.x, 2))

        self.mutable_l0 = _MutableSumExpression([])
        self.mutable_l1 = _MutableLinearExpression([self.mon_npv])
        self.mutable_l2 = _MutableSumExpression([self.mon_npv, self.other])
        self.mutable_l3 = _MutableNPVSumExpression([self.npv])
        self.l0 = 0
        self.l1 = self.mon_npv
        self.l2 = SumExpression([self.mon_npv, self.other])
        self.l3 = self.npv

        # often repeated reference expressions
        self.minus_bin = MonomialTermExpression((-1, self.bin))
        self.minus_npv = NPV_NegationExpression((self.npv,))
        self.minus_param_mut = NPV_NegationExpression((self.param_mut,))
        self.minus_var = MonomialTermExpression((-1, self.var))
        self.minus_mon_native = MonomialTermExpression((-3, self.mon_native.arg(1)))
        self.minus_mon_param = MonomialTermExpression(
            (NPV_NegationExpression((self.mon_param.arg(0),)), self.mon_param.arg(1))
        )
        self.minus_mon_npv = MonomialTermExpression(
            (NPV_NegationExpression((self.mon_npv.arg(0),)), self.mon_npv.arg(1))
        )
        self.minus_linear = NegationExpression((self.linear,))
        self.minus_sum = NegationExpression((self.sum,))
        self.minus_other = NegationExpression((self.other,))
        self.minus_l2 = NegationExpression((self.l2,))

        self.TEMPLATE = [
            self.invalid,
            self.asbinary,
            self.zero,
            self.one,
            # 4:
            self.native,
            self.npv,
            self.param,
            self.param_mut,
            # 8:
            self.var,
            self.mon_native,
            self.mon_param,
            self.mon_npv,
            # 12:
            self.linear,
            self.sum,
            self.other,
            self.mutable_l0,
            # 16:
            self.mutable_l1,
            self.mutable_l2,
            self.param0,
            self.param1,
            # 20:
            self.mutable_l3,
        ]

    def _print_error(self, test_num, test, ans):
        msg = f"Failed test {test_num}:\n\t"
        for arg in test:
            try:
                msg += str(arg)
            except:
                msg += '[ERROR]'
            msg += f'  ({arg.__class__.__name__}'
            try:
                msg += f': {arg.nargs()}'
            except AttributeError:
                pass
            msg += ')\n\t'
        msg += f'{ans} (result: {ans.__class__.__name__}'
        try:
            msg += f': {ans.nargs()}'
        except AttributeError:
            pass
        msg += ')'
        logger.error(msg)

    def _run_cases(self, tests, op):
        self.assertEqual(len(tests), self.NUM_TESTS)
        # Check that this test is checking the correct arg type (catch
        # copy/paste errors)
        ref = tests[0][:-2]
        for test_num, test in enumerate(tests):
            self.assertIs(test[-2], self.TEMPLATE[test_num])
            for i, j in zip(test[:-2], ref):
                self.assertIs(i, j)
        try:
            for test_num, test in enumerate(tests):
                ans = None
                args = [clone_expression(arg) for arg in test[:-1]]
                result = clone_expression(test[-1])
                if result is self.SKIP:
                    continue
                orig_args = list(args)
                orig_args_clone = [clone_expression(arg) for arg in args]
                try:
                    mutable = [isinstance(arg, _MutableSumExpression) for arg in args]
                    classes = [arg.__class__ for arg in args]
                    with LoggingIntercept() as LOG:
                        ans = op(*args)
                    if not any(arg is self.asbinary for arg in test):
                        self.assertEqual(LOG.getvalue(), "")
                    assertExpressionsEqual(self, result, ans)
                    for i, arg in enumerate(args):
                        self.assertFalse(isinstance(arg, _MutableSumExpression))
                        self.assertIs(arg, orig_args[i])
                        if mutable[i]:
                            self.assertIsNot(arg.__class__, classes[i])
                            assertExpressionsEqual(
                                self,
                                _MutableSumExpression(arg.args),
                                _MutableSumExpression(orig_args_clone[i].args),
                            )
                        else:
                            self.assertIs(arg.__class__, classes[i])
                            assertExpressionsEqual(self, arg, orig_args_clone[i])
                except TypeError as e:
                    if type(result) is str:
                        self.assertRegex(str(e), result)
                    elif result is not NotImplemented:
                        raise
                except ZeroDivisionError:
                    if result is not ZeroDivisionError:
                        raise
                except ValueError:
                    if result is not ValueError:
                        raise
                finally:
                    for i, arg in enumerate(args):
                        if mutable[i]:
                            arg.__class__ = classes[i]
        except:
            self._print_error(test_num, test, ans)
            raise


class BaseNumeric(Base):
    def tearDown(self):
        # Restore the state of 0 and 1 optimizations
        _zero_one_optimizations.clear()
        _zero_one_optimizations.update(self.zero_one_optimizations)

        return super().tearDown()

    def setUp(self):
        # Save (and standardize) the state of 0 and 1 optimizations
        self.zero_one_optimizations = set(_zero_one_optimizations)

        return super().setUp()

    def _run_iadd_cases(self, tests, op):
        self.assertEqual(len(tests), self.NUM_TESTS)
        # Check that this test is checking the correct arg type (catch
        # copy/paste errors)
        ref = tests[0][:-2]
        for test_num, test in enumerate(tests):
            self.assertIs(test[-2], self.TEMPLATE[test_num])
            for i, j in zip(test[:-2], ref):
                self.assertIs(i, j)
        try:
            for test_num, test in enumerate(tests):
                ans = None
                args = [clone_expression(arg) for arg in test[:-1]]
                result = clone_expression(test[-1])
                if result is self.SKIP:
                    continue
                orig_args = list(args)
                orig_args_clone = [clone_expression(arg) for arg in args]
                try:
                    mutable = [isinstance(arg, _MutableSumExpression) for arg in args]
                    classes = [arg.__class__ for arg in args]
                    with LoggingIntercept() as LOG:
                        ans = op(*args)
                    if not any(arg is self.asbinary for arg in test):
                        self.assertEqual(LOG.getvalue(), "")
                    assertExpressionsEqual(self, result, ans)
                    for i, arg in enumerate(args):
                        self.assertIs(arg, orig_args[i])
                        if mutable[i]:
                            if i:
                                self.assertFalse(isinstance(arg, _MutableSumExpression))
                                self.assertIsNot(arg.__class__, classes[i])
                                assertExpressionsEqual(
                                    self,
                                    _MutableSumExpression(arg.args),
                                    _MutableSumExpression(orig_args_clone[i].args),
                                )
                            else:
                                self.assertIsInstance(arg, _MutableSumExpression)
                                self.assertIs(arg, ans)
                        else:
                            self.assertIs(arg.__class__, classes[i])
                            assertExpressionsEqual(self, arg, orig_args_clone[i])
                except TypeError:
                    if result is not NotImplemented:
                        raise
                except ZeroDivisionError:
                    if result is not ZeroDivisionError:
                        raise
                except ValueError:
                    if result is not ValueError:
                        raise
                else:
                    # Don't reset mutable args yet: we need to print out the error!
                    for i, arg in enumerate(args):
                        if mutable[i]:
                            arg.__class__ = classes[i]
                            arg._args_ = orig_args_clone[i]._args_
                            arg._nargs = orig_args_clone[i]._nargs
        except:
            self._print_error(test_num, orig_args_clone + [result], ans)
            for i, arg in enumerate(args):
                if mutable[i]:
                    arg.__class__ = classes[i]
                    arg._args_ = orig_args_clone[i]._args_
                    arg._nargs = orig_args_clone[i]._nargs
            raise


class TestExpressionGeneration(BaseNumeric, unittest.TestCase):
    def setUp(self):
        super().setUp()
        enable_expression_optimizations(zero=False, one=True)

    #
    #
    # ADDITION
    #
    #

    def test_add_invalid(self):
        tests = [
            # "invalid(str) + invalid(str)" is a legitimate Python
            # operation and should never hit the Pyomo expression
            # system
            (self.invalid, self.invalid, self.SKIP),
            (self.invalid, self.asbinary, NotImplemented),
            (self.invalid, self.zero, NotImplemented),
            (self.invalid, self.one, NotImplemented),
            # 4:
            (self.invalid, self.native, NotImplemented),
            (self.invalid, self.npv, NotImplemented),
            (self.invalid, self.param, NotImplemented),
            (self.invalid, self.param_mut, NotImplemented),
            # 8:
            (self.invalid, self.var, NotImplemented),
            (self.invalid, self.mon_native, NotImplemented),
            (self.invalid, self.mon_param, NotImplemented),
            (self.invalid, self.mon_npv, NotImplemented),
            # 12:
            (self.invalid, self.linear, NotImplemented),
            (self.invalid, self.sum, NotImplemented),
            (self.invalid, self.other, NotImplemented),
            (self.invalid, self.mutable_l0, NotImplemented),
            # 16:
            (self.invalid, self.mutable_l1, NotImplemented),
            (self.invalid, self.mutable_l2, NotImplemented),
            (self.invalid, self.param0, NotImplemented),
            (self.invalid, self.param1, NotImplemented),
            # 20:
            (self.invalid, self.mutable_l3, NotImplemented),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_asbinary(self):
        tests = [
            (self.asbinary, self.invalid, NotImplemented),
            # BooleanVar objects do not support addition
            (self.asbinary, self.asbinary, NotImplemented),
            (self.asbinary, self.zero, self.bin),
            (self.asbinary, self.one, LinearExpression([self.bin, 1])),
            # 4:
            (self.asbinary, self.native, LinearExpression([self.bin, 5])),
            (self.asbinary, self.npv, LinearExpression([self.bin, self.npv])),
            (self.asbinary, self.param, LinearExpression([self.bin, 6])),
            (
                self.asbinary,
                self.param_mut,
                LinearExpression([self.bin, self.param_mut]),
            ),
            # 8:
            (self.asbinary, self.var, LinearExpression([self.bin, self.var])),
            (
                self.asbinary,
                self.mon_native,
                LinearExpression([self.bin, self.mon_native]),
            ),
            (
                self.asbinary,
                self.mon_param,
                LinearExpression([self.bin, self.mon_param]),
            ),
            (self.asbinary, self.mon_npv, LinearExpression([self.bin, self.mon_npv])),
            # 12:
            (
                self.asbinary,
                self.linear,
                LinearExpression(self.linear.args + [self.bin]),
            ),
            (self.asbinary, self.sum, SumExpression(self.sum.args + [self.bin])),
            (self.asbinary, self.other, SumExpression([self.bin, self.other])),
            (self.asbinary, self.mutable_l0, self.bin),
            # 16:
            (
                self.asbinary,
                self.mutable_l1,
                LinearExpression([self.bin, self.mon_npv]),
            ),
            (self.asbinary, self.mutable_l2, SumExpression(self.l2.args + [self.bin])),
            (self.asbinary, self.param0, self.bin),
            (self.asbinary, self.param1, LinearExpression([self.bin, 1])),
            # 20:
            (self.asbinary, self.mutable_l3, LinearExpression([self.bin, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_zero(self):
        tests = [
            (self.zero, self.invalid, NotImplemented),
            (self.zero, self.asbinary, self.bin),
            (self.zero, self.zero, 0),
            (self.zero, self.one, 1),
            # 4:
            (self.zero, self.native, 5),
            (self.zero, self.npv, self.npv),
            (self.zero, self.param, 6),
            (self.zero, self.param_mut, self.param_mut),
            # 8:
            (self.zero, self.var, self.var),
            (self.zero, self.mon_native, self.mon_native),
            (self.zero, self.mon_param, self.mon_param),
            (self.zero, self.mon_npv, self.mon_npv),
            # 12:
            (self.zero, self.linear, self.linear),
            (self.zero, self.sum, self.sum),
            (self.zero, self.other, self.other),
            (self.zero, self.mutable_l0, 0),
            # 16:
            (self.zero, self.mutable_l1, self.mon_npv),
            (self.zero, self.mutable_l2, self.l2),
            (self.zero, self.param0, 0),
            (self.zero, self.param1, 1),
            # 20:
            (self.zero, self.mutable_l3, self.npv),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_one(self):
        tests = [
            (self.one, self.invalid, NotImplemented),
            (self.one, self.asbinary, LinearExpression([1, self.bin])),
            (self.one, self.zero, 1),
            (self.one, self.one, 2),
            # 4:
            (self.one, self.native, 6),
            (self.one, self.npv, NPV_SumExpression([1, self.npv])),
            (self.one, self.param, 7),
            (self.one, self.param_mut, NPV_SumExpression([1, self.param_mut])),
            # 8:
            (self.one, self.var, LinearExpression([1, self.var])),
            (self.one, self.mon_native, LinearExpression([1, self.mon_native])),
            (self.one, self.mon_param, LinearExpression([1, self.mon_param])),
            (self.one, self.mon_npv, LinearExpression([1, self.mon_npv])),
            # 12:
            (self.one, self.linear, LinearExpression(self.linear.args + [1])),
            (self.one, self.sum, SumExpression(self.sum.args + [1])),
            (self.one, self.other, SumExpression([1, self.other])),
            (self.one, self.mutable_l0, 1),
            # 16:
            (self.one, self.mutable_l1, LinearExpression([1, self.l1])),
            (self.one, self.mutable_l2, SumExpression(self.l2.args + [1])),
            (self.one, self.param0, 1),
            (self.one, self.param1, 2),
            # 20:
            (self.one, self.mutable_l3, NPV_SumExpression([1, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_native(self):
        tests = [
            (self.native, self.invalid, NotImplemented),
            (self.native, self.asbinary, LinearExpression([5, self.bin])),
            (self.native, self.zero, 5),
            (self.native, self.one, 6),
            # 4:
            (self.native, self.native, 10),
            (self.native, self.npv, NPV_SumExpression([5, self.npv])),
            (self.native, self.param, 11),
            (self.native, self.param_mut, NPV_SumExpression([5, self.param_mut])),
            # 8:
            (self.native, self.var, LinearExpression([5, self.var])),
            (self.native, self.mon_native, LinearExpression([5, self.mon_native])),
            (self.native, self.mon_param, LinearExpression([5, self.mon_param])),
            (self.native, self.mon_npv, LinearExpression([5, self.mon_npv])),
            # 12:
            (self.native, self.linear, LinearExpression(self.linear.args + [5])),
            (self.native, self.sum, SumExpression(self.sum.args + [5])),
            (self.native, self.other, SumExpression([5, self.other])),
            (self.native, self.mutable_l0, 5),
            # 16:
            (self.native, self.mutable_l1, LinearExpression([5, self.l1])),
            (self.native, self.mutable_l2, SumExpression(self.l2.args + [5])),
            (self.native, self.param0, 5),
            (self.native, self.param1, 6),
            # 20:
            (self.native, self.mutable_l3, NPV_SumExpression([5, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_npv(self):
        tests = [
            (self.npv, self.invalid, NotImplemented),
            (self.npv, self.asbinary, LinearExpression([self.npv, self.bin])),
            (self.npv, self.zero, self.npv),
            (self.npv, self.one, NPV_SumExpression([self.npv, 1])),
            # 4:
            (self.npv, self.native, NPV_SumExpression([self.npv, 5])),
            (self.npv, self.npv, NPV_SumExpression([self.npv, self.npv])),
            (self.npv, self.param, NPV_SumExpression([self.npv, 6])),
            (self.npv, self.param_mut, NPV_SumExpression([self.npv, self.param_mut])),
            # 8:
            (self.npv, self.var, LinearExpression([self.npv, self.var])),
            (self.npv, self.mon_native, LinearExpression([self.npv, self.mon_native])),
            (self.npv, self.mon_param, LinearExpression([self.npv, self.mon_param])),
            (self.npv, self.mon_npv, LinearExpression([self.npv, self.mon_npv])),
            # 12:
            (self.npv, self.linear, LinearExpression(self.linear.args + [self.npv])),
            (self.npv, self.sum, SumExpression(self.sum.args + [self.npv])),
            (self.npv, self.other, SumExpression([self.npv, self.other])),
            (self.npv, self.mutable_l0, self.npv),
            # 16:
            (self.npv, self.mutable_l1, LinearExpression([self.npv, self.l1])),
            (self.npv, self.mutable_l2, SumExpression(self.l2.args + [self.npv])),
            (self.npv, self.param0, self.npv),
            (self.npv, self.param1, NPV_SumExpression([self.npv, 1])),
            # 20:
            (self.npv, self.mutable_l3, NPV_SumExpression([self.npv, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_param(self):
        tests = [
            (self.param, self.invalid, NotImplemented),
            (self.param, self.asbinary, LinearExpression([6, self.bin])),
            (self.param, self.zero, 6),
            (self.param, self.one, 7),
            # 4:
            (self.param, self.native, 11),
            (self.param, self.npv, NPV_SumExpression([6, self.npv])),
            (self.param, self.param, 12),
            (self.param, self.param_mut, NPV_SumExpression([6, self.param_mut])),
            # 8:
            (self.param, self.var, LinearExpression([6, self.var])),
            (self.param, self.mon_native, LinearExpression([6, self.mon_native])),
            (self.param, self.mon_param, LinearExpression([6, self.mon_param])),
            (self.param, self.mon_npv, LinearExpression([6, self.mon_npv])),
            # 12:
            (self.param, self.linear, LinearExpression(self.linear.args + [6])),
            (self.param, self.sum, SumExpression(self.sum.args + [6])),
            (self.param, self.other, SumExpression([6, self.other])),
            (self.param, self.mutable_l0, 6),
            # 16:
            (self.param, self.mutable_l1, LinearExpression([6, self.l1])),
            (self.param, self.mutable_l2, SumExpression(self.l2.args + [6])),
            (self.param, self.param0, 6),
            (self.param, self.param1, 7),
            # 20:
            (self.param, self.mutable_l3, NPV_SumExpression([6, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_param_mut(self):
        tests = [
            (self.param_mut, self.invalid, NotImplemented),
            (
                self.param_mut,
                self.asbinary,
                LinearExpression([self.param_mut, self.bin]),
            ),
            (self.param_mut, self.zero, self.param_mut),
            (self.param_mut, self.one, NPV_SumExpression([self.param_mut, 1])),
            # 4:
            (self.param_mut, self.native, NPV_SumExpression([self.param_mut, 5])),
            (self.param_mut, self.npv, NPV_SumExpression([self.param_mut, self.npv])),
            (self.param_mut, self.param, NPV_SumExpression([self.param_mut, 6])),
            (
                self.param_mut,
                self.param_mut,
                NPV_SumExpression([self.param_mut, self.param_mut]),
            ),
            # 8:
            (self.param_mut, self.var, LinearExpression([self.param_mut, self.var])),
            (
                self.param_mut,
                self.mon_native,
                LinearExpression([self.param_mut, self.mon_native]),
            ),
            (
                self.param_mut,
                self.mon_param,
                LinearExpression([self.param_mut, self.mon_param]),
            ),
            (
                self.param_mut,
                self.mon_npv,
                LinearExpression([self.param_mut, self.mon_npv]),
            ),
            # 12:
            (
                self.param_mut,
                self.linear,
                LinearExpression(self.linear.args + [self.param_mut]),
            ),
            (self.param_mut, self.sum, SumExpression(self.sum.args + [self.param_mut])),
            (self.param_mut, self.other, SumExpression([self.param_mut, self.other])),
            (self.param_mut, self.mutable_l0, self.param_mut),
            # 16:
            (
                self.param_mut,
                self.mutable_l1,
                LinearExpression([self.param_mut, self.l1]),
            ),
            (
                self.param_mut,
                self.mutable_l2,
                SumExpression(self.l2.args + [self.param_mut]),
            ),
            (self.param_mut, self.param0, self.param_mut),
            (self.param_mut, self.param1, NPV_SumExpression([self.param_mut, 1])),
            # 20:
            (
                self.param_mut,
                self.mutable_l3,
                NPV_SumExpression([self.param_mut, self.npv]),
            ),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_var(self):
        tests = [
            (self.var, self.invalid, NotImplemented),
            (self.var, self.asbinary, LinearExpression([self.var, self.bin])),
            (self.var, self.zero, self.var),
            (self.var, self.one, LinearExpression([self.var, 1])),
            # 4:
            (self.var, self.native, LinearExpression([self.var, 5])),
            (self.var, self.npv, LinearExpression([self.var, self.npv])),
            (self.var, self.param, LinearExpression([self.var, 6])),
            (self.var, self.param_mut, LinearExpression([self.var, self.param_mut])),
            # 8:
            (self.var, self.var, LinearExpression([self.var, self.var])),
            (self.var, self.mon_native, LinearExpression([self.var, self.mon_native])),
            (self.var, self.mon_param, LinearExpression([self.var, self.mon_param])),
            (self.var, self.mon_npv, LinearExpression([self.var, self.mon_npv])),
            # 12:
            (self.var, self.linear, LinearExpression(self.linear.args + [self.var])),
            (self.var, self.sum, SumExpression(self.sum.args + [self.var])),
            (self.var, self.other, SumExpression([self.var, self.other])),
            (self.var, self.mutable_l0, self.var),
            # 16:
            (self.var, self.mutable_l1, LinearExpression([self.var, self.l1])),
            (self.var, self.mutable_l2, SumExpression(self.l2.args + [self.var])),
            (self.var, self.param0, self.var),
            (self.var, self.param1, LinearExpression([self.var, 1])),
            # 20:
            (self.var, self.mutable_l3, LinearExpression([self.var, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_mon_native(self):
        tests = [
            (self.mon_native, self.invalid, NotImplemented),
            (
                self.mon_native,
                self.asbinary,
                LinearExpression([self.mon_native, self.bin]),
            ),
            (self.mon_native, self.zero, self.mon_native),
            (self.mon_native, self.one, LinearExpression([self.mon_native, 1])),
            # 4:
            (self.mon_native, self.native, LinearExpression([self.mon_native, 5])),
            (self.mon_native, self.npv, LinearExpression([self.mon_native, self.npv])),
            (self.mon_native, self.param, LinearExpression([self.mon_native, 6])),
            (
                self.mon_native,
                self.param_mut,
                LinearExpression([self.mon_native, self.param_mut]),
            ),
            # 8:
            (self.mon_native, self.var, LinearExpression([self.mon_native, self.var])),
            (
                self.mon_native,
                self.mon_native,
                LinearExpression([self.mon_native, self.mon_native]),
            ),
            (
                self.mon_native,
                self.mon_param,
                LinearExpression([self.mon_native, self.mon_param]),
            ),
            (
                self.mon_native,
                self.mon_npv,
                LinearExpression([self.mon_native, self.mon_npv]),
            ),
            # 12:
            (
                self.mon_native,
                self.linear,
                LinearExpression(self.linear.args + [self.mon_native]),
            ),
            (
                self.mon_native,
                self.sum,
                SumExpression(self.sum.args + [self.mon_native]),
            ),
            (self.mon_native, self.other, SumExpression([self.mon_native, self.other])),
            (self.mon_native, self.mutable_l0, self.mon_native),
            # 16:
            (
                self.mon_native,
                self.mutable_l1,
                LinearExpression([self.mon_native, self.l1]),
            ),
            (
                self.mon_native,
                self.mutable_l2,
                SumExpression(self.l2.args + [self.mon_native]),
            ),
            (self.mon_native, self.param0, self.mon_native),
            (self.mon_native, self.param1, LinearExpression([self.mon_native, 1])),
            # 20:
            (
                self.mon_native,
                self.mutable_l3,
                LinearExpression([self.mon_native, self.npv]),
            ),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_mon_param(self):
        tests = [
            (self.mon_param, self.invalid, NotImplemented),
            (
                self.mon_param,
                self.asbinary,
                LinearExpression([self.mon_param, self.bin]),
            ),
            (self.mon_param, self.zero, self.mon_param),
            (self.mon_param, self.one, LinearExpression([self.mon_param, 1])),
            # 4:
            (self.mon_param, self.native, LinearExpression([self.mon_param, 5])),
            (self.mon_param, self.npv, LinearExpression([self.mon_param, self.npv])),
            (self.mon_param, self.param, LinearExpression([self.mon_param, 6])),
            (
                self.mon_param,
                self.param_mut,
                LinearExpression([self.mon_param, self.param_mut]),
            ),
            # 8:
            (self.mon_param, self.var, LinearExpression([self.mon_param, self.var])),
            (
                self.mon_param,
                self.mon_native,
                LinearExpression([self.mon_param, self.mon_native]),
            ),
            (
                self.mon_param,
                self.mon_param,
                LinearExpression([self.mon_param, self.mon_param]),
            ),
            (
                self.mon_param,
                self.mon_npv,
                LinearExpression([self.mon_param, self.mon_npv]),
            ),
            # 12:
            (
                self.mon_param,
                self.linear,
                LinearExpression(self.linear.args + [self.mon_param]),
            ),
            (self.mon_param, self.sum, SumExpression(self.sum.args + [self.mon_param])),
            (self.mon_param, self.other, SumExpression([self.mon_param, self.other])),
            (self.mon_param, self.mutable_l0, self.mon_param),
            # 16:
            (
                self.mon_param,
                self.mutable_l1,
                LinearExpression([self.mon_param, self.l1]),
            ),
            (
                self.mon_param,
                self.mutable_l2,
                SumExpression(self.l2.args + [self.mon_param]),
            ),
            (self.mon_param, self.param0, self.mon_param),
            (self.mon_param, self.param1, LinearExpression([self.mon_param, 1])),
            # 20:
            (
                self.mon_param,
                self.mutable_l3,
                LinearExpression([self.mon_param, self.npv]),
            ),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_mon_npv(self):
        tests = [
            (self.mon_npv, self.invalid, NotImplemented),
            (self.mon_npv, self.asbinary, LinearExpression([self.mon_npv, self.bin])),
            (self.mon_npv, self.zero, self.mon_npv),
            (self.mon_npv, self.one, LinearExpression([self.mon_npv, 1])),
            # 4:
            (self.mon_npv, self.native, LinearExpression([self.mon_npv, 5])),
            (self.mon_npv, self.npv, LinearExpression([self.mon_npv, self.npv])),
            (self.mon_npv, self.param, LinearExpression([self.mon_npv, 6])),
            (
                self.mon_npv,
                self.param_mut,
                LinearExpression([self.mon_npv, self.param_mut]),
            ),
            # 8:
            (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.var])),
            (
                self.mon_npv,
                self.mon_native,
                LinearExpression([self.mon_npv, self.mon_native]),
            ),
            (
                self.mon_npv,
                self.mon_param,
                LinearExpression([self.mon_npv, self.mon_param]),
            ),
            (
                self.mon_npv,
                self.mon_npv,
                LinearExpression([self.mon_npv, self.mon_npv]),
            ),
            # 12:
            (
                self.mon_npv,
                self.linear,
                LinearExpression(self.linear.args + [self.mon_npv]),
            ),
            (self.mon_npv, self.sum, SumExpression(self.sum.args + [self.mon_npv])),
            (self.mon_npv, self.other, SumExpression([self.mon_npv, self.other])),
            (self.mon_npv, self.mutable_l0, self.mon_npv),
            # 16:
            (self.mon_npv, self.mutable_l1, LinearExpression([self.mon_npv, self.l1])),
            (
                self.mon_npv,
                self.mutable_l2,
                SumExpression(self.l2.args + [self.mon_npv]),
            ),
            (self.mon_npv, self.param0, self.mon_npv),
            (self.mon_npv, self.param1, LinearExpression([self.mon_npv, 1])),
            # 20:
            (self.mon_npv, self.mutable_l3, LinearExpression([self.mon_npv, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_linear(self):
        tests = [
            (self.linear, self.invalid, NotImplemented),
            (
                self.linear,
                self.asbinary,
                LinearExpression(self.linear.args + [self.bin]),
            ),
            (self.linear, self.zero, self.linear),
            (self.linear, self.one, LinearExpression(self.linear.args + [1])),
            # 4:
            (self.linear, self.native, LinearExpression(self.linear.args + [5])),
            (self.linear, self.npv, LinearExpression(self.linear.args + [self.npv])),
            (self.linear, self.param, LinearExpression(self.linear.args + [6])),
            (
                self.linear,
                self.param_mut,
                LinearExpression(self.linear.args + [self.param_mut]),
            ),
            # 8:
            (self.linear, self.var, LinearExpression(self.linear.args + [self.var])),
            (
                self.linear,
                self.mon_native,
                LinearExpression(self.linear.args + [self.mon_native]),
            ),
            (
                self.linear,
                self.mon_param,
                LinearExpression(self.linear.args + [self.mon_param]),
            ),
            (
                self.linear,
                self.mon_npv,
                LinearExpression(self.linear.args + [self.mon_npv]),
            ),
            # 12:
            (
                self.linear,
                self.linear,
                LinearExpression(self.linear.args + self.linear.args),
            ),
            (self.linear, self.sum, SumExpression(self.sum.args + [self.linear])),
            (self.linear, self.other, SumExpression([self.linear, self.other])),
            (self.linear, self.mutable_l0, self.linear),
            # 16:
            (
                self.linear,
                self.mutable_l1,
                LinearExpression(self.linear.args + [self.l1]),
            ),
            (self.linear, self.mutable_l2, SumExpression(self.l2.args + [self.linear])),
            (self.linear, self.param0, self.linear),
            (self.linear, self.param1, LinearExpression(self.linear.args + [1])),
            # 20:
            (
                self.linear,
                self.mutable_l3,
                LinearExpression(self.linear.args + [self.npv]),
            ),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_sum(self):
        tests = [
            (self.sum, self.invalid, NotImplemented),
            (self.sum, self.asbinary, SumExpression(self.sum.args + [self.bin])),
            (self.sum, self.zero, self.sum),
            (self.sum, self.one, SumExpression(self.sum.args + [1])),
            # 4:
            (self.sum, self.native, SumExpression(self.sum.args + [5])),
            (self.sum, self.npv, SumExpression(self.sum.args + [self.npv])),
            (self.sum, self.param, SumExpression(self.sum.args + [6])),
            (self.sum, self.param_mut, SumExpression(self.sum.args + [self.param_mut])),
            # 8:
            (self.sum, self.var, SumExpression(self.sum.args + [self.var])),
            (
                self.sum,
                self.mon_native,
                SumExpression(self.sum.args + [self.mon_native]),
            ),
            (self.sum, self.mon_param, SumExpression(self.sum.args + [self.mon_param])),
            (self.sum, self.mon_npv, SumExpression(self.sum.args + [self.mon_npv])),
            # 12:
            (self.sum, self.linear, SumExpression(self.sum.args + [self.linear])),
            (self.sum, self.sum, SumExpression(self.sum.args + self.sum.args)),
            (self.sum, self.other, SumExpression(self.sum.args + [self.other])),
            (self.sum, self.mutable_l0, self.sum),
            # 16:
            (self.sum, self.mutable_l1, SumExpression(self.sum.args + [self.l1])),
            (self.sum, self.mutable_l2, SumExpression(self.sum.args + self.l2.args)),
            (self.sum, self.param0, self.sum),
            (self.sum, self.param1, SumExpression(self.sum.args + [1])),
            # 20:
            (self.sum, self.mutable_l3, SumExpression(self.sum.args + [self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_other(self):
        tests = [
            (self.other, self.invalid, NotImplemented),
            (self.other, self.asbinary, SumExpression([self.other, self.bin])),
            (self.other, self.zero, self.other),
            (self.other, self.one, SumExpression([self.other, 1])),
            # 4:
            (self.other, self.native, SumExpression([self.other, 5])),
            (self.other, self.npv, SumExpression([self.other, self.npv])),
            (self.other, self.param, SumExpression([self.other, 6])),
            (self.other, self.param_mut, SumExpression([self.other, self.param_mut])),
            # 8:
            (self.other, self.var, SumExpression([self.other, self.var])),
            (self.other, self.mon_native, SumExpression([self.other, self.mon_native])),
            (self.other, self.mon_param, SumExpression([self.other, self.mon_param])),
            (self.other, self.mon_npv, SumExpression([self.other, self.mon_npv])),
            # 12:
            (self.other, self.linear, SumExpression([self.other, self.linear])),
            (self.other, self.sum, SumExpression(self.sum.args + [self.other])),
            (self.other, self.other, SumExpression([self.other, self.other])),
            (self.other, self.mutable_l0, self.other),
            # 16:
            (self.other, self.mutable_l1, SumExpression([self.other, self.mon_npv])),
            (self.other, self.mutable_l2, SumExpression(self.l2.args + [self.other])),
            (self.other, self.param0, self.other),
            (self.other, self.param1, SumExpression([self.other, 1])),
            # 20:
            (self.other, self.mutable_l3, SumExpression([self.other, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_mutable_l0(self):
        tests = [
            (self.mutable_l0, self.invalid, NotImplemented),
            (self.mutable_l0, self.asbinary, self.bin),
            (self.mutable_l0, self.zero, 0),
            (self.mutable_l0, self.one, 1),
            # 4:
            (self.mutable_l0, self.native, 5),
            (self.mutable_l0, self.npv, self.npv),
            (self.mutable_l0, self.param, 6),
            (self.mutable_l0, self.param_mut, self.param_mut),
            # 8:
            (self.mutable_l0, self.var, self.var),
            (self.mutable_l0, self.mon_native, self.mon_native),
            (self.mutable_l0, self.mon_param, self.mon_param),
            (self.mutable_l0, self.mon_npv, self.mon_npv),
            # 12:
            (self.mutable_l0, self.linear, self.linear),
            (self.mutable_l0, self.sum, self.sum),
            (self.mutable_l0, self.other, self.other),
            (self.mutable_l0, self.mutable_l0, 0),
            # 16:
            (self.mutable_l0, self.mutable_l1, self.mon_npv),
            (self.mutable_l0, self.mutable_l2, self.l2),
            (self.mutable_l0, self.param0, 0),
            (self.mutable_l0, self.param1, 1),
            # 20:
            (self.mutable_l0, self.mutable_l3, self.npv),
        ]
        self._run_cases(tests, operator.add)
        # Mutable iadd handled by separate tests
        # self._run_cases(tests, operator.iadd)

    def test_add_mutable_l1(self):
        tests = [
            (self.mutable_l1, self.invalid, NotImplemented),
            (self.mutable_l1, self.asbinary, LinearExpression([self.l1, self.bin])),
            (self.mutable_l1, self.zero, self.mon_npv),
            (self.mutable_l1, self.one, LinearExpression([self.l1, 1])),
            # 4:
            (self.mutable_l1, self.native, LinearExpression([self.l1, 5])),
            (self.mutable_l1, self.npv, LinearExpression([self.l1, self.npv])),
            (self.mutable_l1, self.param, LinearExpression([self.l1, 6])),
            (
                self.mutable_l1,
                self.param_mut,
                LinearExpression([self.l1, self.param_mut]),
            ),
            # 8:
            (self.mutable_l1, self.var, LinearExpression([self.l1, self.var])),
            (
                self.mutable_l1,
                self.mon_native,
                LinearExpression([self.l1, self.mon_native]),
            ),
            (
                self.mutable_l1,
                self.mon_param,
                LinearExpression([self.l1, self.mon_param]),
            ),
            (self.mutable_l1, self.mon_npv, LinearExpression([self.l1, self.mon_npv])),
            # 12:
            (
                self.mutable_l1,
                self.linear,
                LinearExpression(self.linear.args + [self.l1]),
            ),
            (self.mutable_l1, self.sum, SumExpression(self.sum.args + [self.l1])),
            (self.mutable_l1, self.other, SumExpression([self.l1, self.other])),
            (self.mutable_l1, self.mutable_l0, self.mon_npv),
            # 16:
            (self.mutable_l1, self.mutable_l1, LinearExpression([self.l1, self.l1])),
            (self.mutable_l1, self.mutable_l2, SumExpression(self.l2.args + [self.l1])),
            (self.mutable_l1, self.param0, self.mon_npv),
            (self.mutable_l1, self.param1, LinearExpression([self.l1, 1])),
            # 20:
            (self.mutable_l1, self.mutable_l3, LinearExpression([self.l1, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        # Mutable iadd handled by separate tests
        # self._run_cases(tests, operator.iadd)

    def test_add_mutable_l2(self):
        tests = [
            (self.mutable_l2, self.invalid, NotImplemented),
            (self.mutable_l2, self.asbinary, SumExpression(self.l2.args + [self.bin])),
            (self.mutable_l2, self.zero, self.l2),
            (self.mutable_l2, self.one, SumExpression(self.l2.args + [1])),
            # 4:
            (self.mutable_l2, self.native, SumExpression(self.l2.args + [5])),
            (self.mutable_l2, self.npv, SumExpression(self.l2.args + [self.npv])),
            (self.mutable_l2, self.param, SumExpression(self.l2.args + [6])),
            (
                self.mutable_l2,
                self.param_mut,
                SumExpression(self.l2.args + [self.param_mut]),
            ),
            # 8:
            (self.mutable_l2, self.var, SumExpression(self.l2.args + [self.var])),
            (
                self.mutable_l2,
                self.mon_native,
                SumExpression(self.l2.args + [self.mon_native]),
            ),
            (
                self.mutable_l2,
                self.mon_param,
                SumExpression(self.l2.args + [self.mon_param]),
            ),
            (
                self.mutable_l2,
                self.mon_npv,
                SumExpression(self.l2.args + [self.mon_npv]),
            ),
            # 12:
            (self.mutable_l2, self.linear, SumExpression(self.l2.args + [self.linear])),
            (self.mutable_l2, self.sum, SumExpression(self.l2.args + self.sum.args)),
            (self.mutable_l2, self.other, SumExpression(self.l2.args + [self.other])),
            (self.mutable_l2, self.mutable_l0, self.l2),
            # 16:
            (self.mutable_l2, self.mutable_l1, SumExpression(self.l2.args + [self.l1])),
            (
                self.mutable_l2,
                self.mutable_l2,
                SumExpression(self.l2.args + self.l2.args),
            ),
            (self.mutable_l2, self.param0, self.l2),
            (self.mutable_l2, self.param1, SumExpression(self.l2.args + [1])),
            # 20:
            (
                self.mutable_l2,
                self.mutable_l3,
                SumExpression(self.l2.args + [self.npv]),
            ),
        ]
        self._run_cases(tests, operator.add)
        # Mutable iadd handled by separate tests
        # self._run_cases(tests, operator.iadd)

    def test_add_param0(self):
        tests = [
            (self.param0, self.invalid, NotImplemented),
            (self.param0, self.asbinary, self.bin),
            (self.param0, self.zero, 0),
            (self.param0, self.one, 1),
            # 4:
            (self.param0, self.native, 5),
            (self.param0, self.npv, self.npv),
            (self.param0, self.param, 6),
            (self.param0, self.param_mut, self.param_mut),
            # 8:
            (self.param0, self.var, self.var),
            (self.param0, self.mon_native, self.mon_native),
            (self.param0, self.mon_param, self.mon_param),
            (self.param0, self.mon_npv, self.mon_npv),
            # 12:
            (self.param0, self.linear, self.linear),
            (self.param0, self.sum, self.sum),
            (self.param0, self.other, self.other),
            (self.param0, self.mutable_l0, 0),
            # 16:
            (self.param0, self.mutable_l1, self.mon_npv),
            (self.param0, self.mutable_l2, self.l2),
            (self.param0, self.param0, 0),
            (self.param0, self.param1, 1),
            # 20:
            (self.param0, self.mutable_l3, self.npv),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_param1(self):
        tests = [
            (self.param1, self.invalid, NotImplemented),
            (self.param1, self.asbinary, LinearExpression([1, self.bin])),
            (self.param1, self.zero, 1),
            (self.param1, self.one, 2),
            # 4:
            (self.param1, self.native, 6),
            (self.param1, self.npv, NPV_SumExpression([1, self.npv])),
            (self.param1, self.param, 7),
            (self.param1, self.param_mut, NPV_SumExpression([1, self.param_mut])),
            # 8:
            (self.param1, self.var, LinearExpression([1, self.var])),
            (self.param1, self.mon_native, LinearExpression([1, self.mon_native])),
            (self.param1, self.mon_param, LinearExpression([1, self.mon_param])),
            (self.param1, self.mon_npv, LinearExpression([1, self.mon_npv])),
            # 12:
            (self.param1, self.linear, LinearExpression(self.linear.args + [1])),
            (self.param1, self.sum, SumExpression(self.sum.args + [1])),
            (self.param1, self.other, SumExpression([1, self.other])),
            (self.param1, self.mutable_l0, 1),
            # 16:
            (self.param1, self.mutable_l1, LinearExpression([1, self.l1])),
            (self.param1, self.mutable_l2, SumExpression(self.l2.args + [1])),
            (self.param1, self.param0, 1),
            (self.param1, self.param1, 2),
            # 20:
            (self.param1, self.mutable_l3, NPV_SumExpression([1, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        self._run_cases(tests, operator.iadd)

    def test_add_mutable_l3(self):
        tests = [
            (self.mutable_l3, self.invalid, NotImplemented),
            (self.mutable_l3, self.asbinary, LinearExpression([self.l3, self.bin])),
            (self.mutable_l3, self.zero, self.npv),
            (self.mutable_l3, self.one, NPV_SumExpression([self.l3, 1])),
            # 4:
            (self.mutable_l3, self.native, NPV_SumExpression([self.l3, 5])),
            (self.mutable_l3, self.npv, NPV_SumExpression([self.l3, self.npv])),
            (self.mutable_l3, self.param, NPV_SumExpression([self.l3, 6])),
            (
                self.mutable_l3,
                self.param_mut,
                NPV_SumExpression([self.l3, self.param_mut]),
            ),
            # 8:
            (self.mutable_l3, self.var, LinearExpression([self.l3, self.var])),
            (
                self.mutable_l3,
                self.mon_native,
                LinearExpression([self.l3, self.mon_native]),
            ),
            (
                self.mutable_l3,
                self.mon_param,
                LinearExpression([self.l3, self.mon_param]),
            ),
            (self.mutable_l3, self.mon_npv, LinearExpression([self.l3, self.mon_npv])),
            # 12:
            (
                self.mutable_l3,
                self.linear,
                LinearExpression(self.linear.args + [self.l3]),
            ),
            (self.mutable_l3, self.sum, SumExpression(self.sum.args + [self.l3])),
            (self.mutable_l3, self.other, SumExpression([self.l3, self.other])),
            (self.mutable_l3, self.mutable_l0, self.npv),
            # 16:
            (self.mutable_l3, self.mutable_l1, LinearExpression([self.l3, self.l1])),
            (self.mutable_l3, self.mutable_l2, SumExpression(self.l2.args + [self.l3])),
            (self.mutable_l3, self.param0, self.npv),
            (self.mutable_l3, self.param1, NPV_SumExpression([self.l3, 1])),
            # 20:
            (self.mutable_l3, self.mutable_l3, NPV_SumExpression([self.l3, self.npv])),
        ]
        self._run_cases(tests, operator.add)
        # Mutable iadd handled by separate tests
        # self._run_cases(tests, operator.iadd)

    #
    #
    # SUBTRACTION
    #
    #

    def test_sub_invalid(self):
        tests = [
            (self.invalid, self.invalid, NotImplemented),
            (self.invalid, self.asbinary, NotImplemented),
            (self.invalid, self.zero, NotImplemented),
            (self.invalid, self.one, NotImplemented),
            # 4:
            (self.invalid, self.native, NotImplemented),
            (self.invalid, self.npv, NotImplemented),
            (self.invalid, self.param, NotImplemented),
            (self.invalid, self.param_mut, NotImplemented),
            # 8:
            (self.invalid, self.var, NotImplemented),
            (self.invalid, self.mon_native, NotImplemented),
            (self.invalid, self.mon_param, NotImplemented),
            (self.invalid, self.mon_npv, NotImplemented),
            # 12:
            (self.invalid, self.linear, NotImplemented),
            (self.invalid, self.sum, NotImplemented),
            (self.invalid, self.other, NotImplemented),
            (self.invalid, self.mutable_l0, NotImplemented),
            # 16:
            (self.invalid, self.mutable_l1, NotImplemented),
            (self.invalid, self.mutable_l2, NotImplemented),
            (self.invalid, self.param0, NotImplemented),
            (self.invalid, self.param1, NotImplemented),
            # 20:
            (self.invalid, self.mutable_l3, NotImplemented),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_asbinary(self):
        tests = [
            (self.asbinary, self.invalid, NotImplemented),
            # BooleanVar objects do not support addition
            (self.asbinary, self.asbinary, NotImplemented),
            (self.asbinary, self.zero, self.bin),
            (self.asbinary, self.one, LinearExpression([self.bin, -1])),
            # 4:
            (self.asbinary, self.native, LinearExpression([self.bin, -5])),
            (self.asbinary, self.npv, LinearExpression([self.bin, self.minus_npv])),
            (self.asbinary, self.param, LinearExpression([self.bin, -6])),
            (
                self.asbinary,
                self.param_mut,
                LinearExpression([self.bin, self.minus_param_mut]),
            ),
            # 8:
            (self.asbinary, self.var, LinearExpression([self.bin, self.minus_var])),
            (
                self.asbinary,
                self.mon_native,
                LinearExpression([self.bin, self.minus_mon_native]),
            ),
            (
                self.asbinary,
                self.mon_param,
                LinearExpression([self.bin, self.minus_mon_param]),
            ),
            (
                self.asbinary,
                self.mon_npv,
                LinearExpression([self.bin, self.minus_mon_npv]),
            ),
            # 12:
            (self.asbinary, self.linear, SumExpression([self.bin, self.minus_linear])),
            (self.asbinary, self.sum, SumExpression([self.bin, self.minus_sum])),
            (self.asbinary, self.other, SumExpression([self.bin, self.minus_other])),
            (self.asbinary, self.mutable_l0, self.bin),
            # 16:
            (
                self.asbinary,
                self.mutable_l1,
                LinearExpression([self.bin, self.minus_mon_npv]),
            ),
            (self.asbinary, self.mutable_l2, SumExpression([self.bin, self.minus_l2])),
            (self.asbinary, self.param0, self.bin),
            (self.asbinary, self.param1, LinearExpression([self.bin, -1])),
            # 20:
            (
                self.asbinary,
                self.mutable_l3,
                LinearExpression([self.bin, self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_zero(self):
        tests = [
            (self.zero, self.invalid, NotImplemented),
            (self.zero, self.asbinary, self.minus_bin),
            (self.zero, self.zero, 0),
            (self.zero, self.one, -1),
            # 4:
            (self.zero, self.native, -5),
            (self.zero, self.npv, self.minus_npv),
            (self.zero, self.param, -6),
            (self.zero, self.param_mut, self.minus_param_mut),
            # 8:
            (self.zero, self.var, self.minus_var),
            (self.zero, self.mon_native, self.minus_mon_native),
            (self.zero, self.mon_param, self.minus_mon_param),
            (self.zero, self.mon_npv, self.minus_mon_npv),
            # 12:
            (self.zero, self.linear, self.minus_linear),
            (self.zero, self.sum, self.minus_sum),
            (self.zero, self.other, self.minus_other),
            (self.zero, self.mutable_l0, 0),
            # 16:
            (self.zero, self.mutable_l1, self.minus_mon_npv),
            (self.zero, self.mutable_l2, self.minus_l2),
            (self.zero, self.param0, 0),
            (self.zero, self.param1, -1),
            # 20:
            (self.zero, self.mutable_l3, self.minus_npv),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_one(self):
        tests = [
            (self.one, self.invalid, NotImplemented),
            (self.one, self.asbinary, LinearExpression([1, self.minus_bin])),
            (self.one, self.zero, 1),
            (self.one, self.one, 0),
            # 4:
            (self.one, self.native, -4),
            (self.one, self.npv, NPV_SumExpression([1, self.minus_npv])),
            (self.one, self.param, -5),
            (self.one, self.param_mut, NPV_SumExpression([1, self.minus_param_mut])),
            # 8:
            (self.one, self.var, LinearExpression([1, self.minus_var])),
            (self.one, self.mon_native, LinearExpression([1, self.minus_mon_native])),
            (self.one, self.mon_param, LinearExpression([1, self.minus_mon_param])),
            (self.one, self.mon_npv, LinearExpression([1, self.minus_mon_npv])),
            # 12:
            (self.one, self.linear, SumExpression([1, self.minus_linear])),
            (self.one, self.sum, SumExpression([1, self.minus_sum])),
            (self.one, self.other, SumExpression([1, self.minus_other])),
            (self.one, self.mutable_l0, 1),
            # 16:
            (self.one, self.mutable_l1, LinearExpression([1, self.minus_mon_npv])),
            (self.one, self.mutable_l2, SumExpression([1, self.minus_l2])),
            (self.one, self.param0, 1),
            (self.one, self.param1, 0),
            # 20:
            (self.one, self.mutable_l3, NPV_SumExpression([1, self.minus_npv])),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_native(self):
        tests = [
            (self.native, self.invalid, NotImplemented),
            (self.native, self.asbinary, LinearExpression([5, self.minus_bin])),
            (self.native, self.zero, 5),
            (self.native, self.one, 4),
            # 4:
            (self.native, self.native, 0),
            (self.native, self.npv, NPV_SumExpression([5, self.minus_npv])),
            (self.native, self.param, -1),
            (self.native, self.param_mut, NPV_SumExpression([5, self.minus_param_mut])),
            # 8:
            (self.native, self.var, LinearExpression([5, self.minus_var])),
            (
                self.native,
                self.mon_native,
                LinearExpression([5, self.minus_mon_native]),
            ),
            (self.native, self.mon_param, LinearExpression([5, self.minus_mon_param])),
            (self.native, self.mon_npv, LinearExpression([5, self.minus_mon_npv])),
            # 12:
            (self.native, self.linear, SumExpression([5, self.minus_linear])),
            (self.native, self.sum, SumExpression([5, self.minus_sum])),
            (self.native, self.other, SumExpression([5, self.minus_other])),
            (self.native, self.mutable_l0, 5),
            # 16:
            (self.native, self.mutable_l1, LinearExpression([5, self.minus_mon_npv])),
            (self.native, self.mutable_l2, SumExpression([5, self.minus_l2])),
            (self.native, self.param0, 5),
            (self.native, self.param1, 4),
            # 20:
            (self.native, self.mutable_l3, NPV_SumExpression([5, self.minus_npv])),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_npv(self):
        tests = [
            (self.npv, self.invalid, NotImplemented),
            (self.npv, self.asbinary, LinearExpression([self.npv, self.minus_bin])),
            (self.npv, self.zero, self.npv),
            (self.npv, self.one, NPV_SumExpression([self.npv, -1])),
            # 4:
            (self.npv, self.native, NPV_SumExpression([self.npv, -5])),
            (self.npv, self.npv, NPV_SumExpression([self.npv, self.minus_npv])),
            (self.npv, self.param, NPV_SumExpression([self.npv, -6])),
            (
                self.npv,
                self.param_mut,
                NPV_SumExpression([self.npv, self.minus_param_mut]),
            ),
            # 8:
            (self.npv, self.var, LinearExpression([self.npv, self.minus_var])),
            (
                self.npv,
                self.mon_native,
                LinearExpression([self.npv, self.minus_mon_native]),
            ),
            (
                self.npv,
                self.mon_param,
                LinearExpression([self.npv, self.minus_mon_param]),
            ),
            (self.npv, self.mon_npv, LinearExpression([self.npv, self.minus_mon_npv])),
            # 12:
            (self.npv, self.linear, SumExpression([self.npv, self.minus_linear])),
            (self.npv, self.sum, SumExpression([self.npv, self.minus_sum])),
            (self.npv, self.other, SumExpression([self.npv, self.minus_other])),
            (self.npv, self.mutable_l0, self.npv),
            # 16:
            (
                self.npv,
                self.mutable_l1,
                LinearExpression([self.npv, self.minus_mon_npv]),
            ),
            (self.npv, self.mutable_l2, SumExpression([self.npv, self.minus_l2])),
            (self.npv, self.param0, self.npv),
            (self.npv, self.param1, NPV_SumExpression([self.npv, -1])),
            # 20:
            (self.npv, self.mutable_l3, NPV_SumExpression([self.npv, self.minus_npv])),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_param(self):
        tests = [
            (self.param, self.invalid, NotImplemented),
            (self.param, self.asbinary, LinearExpression([6, self.minus_bin])),
            (self.param, self.zero, 6),
            (self.param, self.one, 5),
            # 4:
            (self.param, self.native, 1),
            (self.param, self.npv, NPV_SumExpression([6, self.minus_npv])),
            (self.param, self.param, 0),
            (self.param, self.param_mut, NPV_SumExpression([6, self.minus_param_mut])),
            # 8:
            (self.param, self.var, LinearExpression([6, self.minus_var])),
            (self.param, self.mon_native, LinearExpression([6, self.minus_mon_native])),
            (self.param, self.mon_param, LinearExpression([6, self.minus_mon_param])),
            (self.param, self.mon_npv, LinearExpression([6, self.minus_mon_npv])),
            # 12:
            (self.param, self.linear, SumExpression([6, self.minus_linear])),
            (self.param, self.sum, SumExpression([6, self.minus_sum])),
            (self.param, self.other, SumExpression([6, self.minus_other])),
            (self.param, self.mutable_l0, 6),
            # 16:
            (self.param, self.mutable_l1, LinearExpression([6, self.minus_mon_npv])),
            (self.param, self.mutable_l2, SumExpression([6, self.minus_l2])),
            (self.param, self.param0, 6),
            (self.param, self.param1, 5),
            # 20:
            (self.param, self.mutable_l3, NPV_SumExpression([6, self.minus_npv])),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_param_mut(self):
        tests = [
            (self.param_mut, self.invalid, NotImplemented),
            (
                self.param_mut,
                self.asbinary,
                LinearExpression([self.param_mut, self.minus_bin]),
            ),
            (self.param_mut, self.zero, self.param_mut),
            (self.param_mut, self.one, NPV_SumExpression([self.param_mut, -1])),
            # 4:
            (self.param_mut, self.native, NPV_SumExpression([self.param_mut, -5])),
            (
                self.param_mut,
                self.npv,
                NPV_SumExpression([self.param_mut, self.minus_npv]),
            ),
            (self.param_mut, self.param, NPV_SumExpression([self.param_mut, -6])),
            (
                self.param_mut,
                self.param_mut,
                NPV_SumExpression([self.param_mut, self.minus_param_mut]),
            ),
            # 8:
            (
                self.param_mut,
                self.var,
                LinearExpression([self.param_mut, self.minus_var]),
            ),
            (
                self.param_mut,
                self.mon_native,
                LinearExpression([self.param_mut, self.minus_mon_native]),
            ),
            (
                self.param_mut,
                self.mon_param,
                LinearExpression([self.param_mut, self.minus_mon_param]),
            ),
            (
                self.param_mut,
                self.mon_npv,
                LinearExpression([self.param_mut, self.minus_mon_npv]),
            ),
            # 12:
            (
                self.param_mut,
                self.linear,
                SumExpression([self.param_mut, self.minus_linear]),
            ),
            (self.param_mut, self.sum, SumExpression([self.param_mut, self.minus_sum])),
            (
                self.param_mut,
                self.other,
                SumExpression([self.param_mut, self.minus_other]),
            ),
            (self.param_mut, self.mutable_l0, self.param_mut),
            # 16:
            (
                self.param_mut,
                self.mutable_l1,
                LinearExpression([self.param_mut, self.minus_mon_npv]),
            ),
            (
                self.param_mut,
                self.mutable_l2,
                SumExpression([self.param_mut, self.minus_l2]),
            ),
            (self.param_mut, self.param0, self.param_mut),
            (self.param_mut, self.param1, NPV_SumExpression([self.param_mut, -1])),
            # 20:
            (
                self.param_mut,
                self.mutable_l3,
                NPV_SumExpression([self.param_mut, self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_var(self):
        tests = [
            (self.var, self.invalid, NotImplemented),
            (self.var, self.asbinary, LinearExpression([self.var, self.minus_bin])),
            (self.var, self.zero, self.var),
            (self.var, self.one, LinearExpression([self.var, -1])),
            # 4:
            (self.var, self.native, LinearExpression([self.var, -5])),
            (self.var, self.npv, LinearExpression([self.var, self.minus_npv])),
            (self.var, self.param, LinearExpression([self.var, -6])),
            (
                self.var,
                self.param_mut,
                LinearExpression([self.var, self.minus_param_mut]),
            ),
            # 8:
            (self.var, self.var, LinearExpression([self.var, self.minus_var])),
            (
                self.var,
                self.mon_native,
                LinearExpression([self.var, self.minus_mon_native]),
            ),
            (
                self.var,
                self.mon_param,
                LinearExpression([self.var, self.minus_mon_param]),
            ),
            (self.var, self.mon_npv, LinearExpression([self.var, self.minus_mon_npv])),
            # 12:
            (
                self.var,
                self.linear,
                SumExpression([self.var, NegationExpression((self.linear,))]),
            ),
            (self.var, self.sum, SumExpression([self.var, self.minus_sum])),
            (self.var, self.other, SumExpression([self.var, self.minus_other])),
            (self.var, self.mutable_l0, self.var),
            # 16:
            (
                self.var,
                self.mutable_l1,
                LinearExpression([self.var, self.minus_mon_npv]),
            ),
            (self.var, self.mutable_l2, SumExpression([self.var, self.minus_l2])),
            (self.var, self.param0, self.var),
            (self.var, self.param1, LinearExpression([self.var, -1])),
            # 20:
            (self.var, self.mutable_l3, LinearExpression([self.var, self.minus_npv])),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_mon_native(self):
        tests = [
            (self.mon_native, self.invalid, NotImplemented),
            (
                self.mon_native,
                self.asbinary,
                LinearExpression([self.mon_native, self.minus_bin]),
            ),
            (self.mon_native, self.zero, self.mon_native),
            (self.mon_native, self.one, LinearExpression([self.mon_native, -1])),
            # 4:
            (self.mon_native, self.native, LinearExpression([self.mon_native, -5])),
            (
                self.mon_native,
                self.npv,
                LinearExpression([self.mon_native, self.minus_npv]),
            ),
            (self.mon_native, self.param, LinearExpression([self.mon_native, -6])),
            (
                self.mon_native,
                self.param_mut,
                LinearExpression([self.mon_native, self.minus_param_mut]),
            ),
            # 8:
            (
                self.mon_native,
                self.var,
                LinearExpression([self.mon_native, self.minus_var]),
            ),
            (
                self.mon_native,
                self.mon_native,
                LinearExpression([self.mon_native, self.minus_mon_native]),
            ),
            (
                self.mon_native,
                self.mon_param,
                LinearExpression([self.mon_native, self.minus_mon_param]),
            ),
            (
                self.mon_native,
                self.mon_npv,
                LinearExpression([self.mon_native, self.minus_mon_npv]),
            ),
            # 12:
            (
                self.mon_native,
                self.linear,
                SumExpression([self.mon_native, self.minus_linear]),
            ),
            (
                self.mon_native,
                self.sum,
                SumExpression([self.mon_native, self.minus_sum]),
            ),
            (
                self.mon_native,
                self.other,
                SumExpression([self.mon_native, self.minus_other]),
            ),
            (self.mon_native, self.mutable_l0, self.mon_native),
            # 16:
            (
                self.mon_native,
                self.mutable_l1,
                LinearExpression([self.mon_native, self.minus_mon_npv]),
            ),
            (
                self.mon_native,
                self.mutable_l2,
                SumExpression([self.mon_native, self.minus_l2]),
            ),
            (self.mon_native, self.param0, self.mon_native),
            (self.mon_native, self.param1, LinearExpression([self.mon_native, -1])),
            # 20:
            (
                self.mon_native,
                self.mutable_l3,
                LinearExpression([self.mon_native, self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_mon_param(self):
        tests = [
            (self.mon_param, self.invalid, NotImplemented),
            (
                self.mon_param,
                self.asbinary,
                LinearExpression([self.mon_param, self.minus_bin]),
            ),
            (self.mon_param, self.zero, self.mon_param),
            (self.mon_param, self.one, LinearExpression([self.mon_param, -1])),
            # 4:
            (self.mon_param, self.native, LinearExpression([self.mon_param, -5])),
            (
                self.mon_param,
                self.npv,
                LinearExpression([self.mon_param, self.minus_npv]),
            ),
            (self.mon_param, self.param, LinearExpression([self.mon_param, -6])),
            (
                self.mon_param,
                self.param_mut,
                LinearExpression([self.mon_param, self.minus_param_mut]),
            ),
            # 8:
            (
                self.mon_param,
                self.var,
                LinearExpression([self.mon_param, self.minus_var]),
            ),
            (
                self.mon_param,
                self.mon_native,
                LinearExpression([self.mon_param, self.minus_mon_native]),
            ),
            (
                self.mon_param,
                self.mon_param,
                LinearExpression([self.mon_param, self.minus_mon_param]),
            ),
            (
                self.mon_param,
                self.mon_npv,
                LinearExpression([self.mon_param, self.minus_mon_npv]),
            ),
            # 12:
            (
                self.mon_param,
                self.linear,
                SumExpression([self.mon_param, self.minus_linear]),
            ),
            (self.mon_param, self.sum, SumExpression([self.mon_param, self.minus_sum])),
            (
                self.mon_param,
                self.other,
                SumExpression([self.mon_param, self.minus_other]),
            ),
            (self.mon_param, self.mutable_l0, self.mon_param),
            # 16:
            (
                self.mon_param,
                self.mutable_l1,
                LinearExpression([self.mon_param, self.minus_mon_npv]),
            ),
            (
                self.mon_param,
                self.mutable_l2,
                SumExpression([self.mon_param, self.minus_l2]),
            ),
            (self.mon_param, self.param0, self.mon_param),
            (self.mon_param, self.param1, LinearExpression([self.mon_param, -1])),
            # 20:
            (
                self.mon_param,
                self.mutable_l3,
                LinearExpression([self.mon_param, self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_mon_npv(self):
        tests = [
            (self.mon_npv, self.invalid, NotImplemented),
            (
                self.mon_npv,
                self.asbinary,
                LinearExpression([self.mon_npv, self.minus_bin]),
            ),
            (self.mon_npv, self.zero, self.mon_npv),
            (self.mon_npv, self.one, LinearExpression([self.mon_npv, -1])),
            # 4:
            (self.mon_npv, self.native, LinearExpression([self.mon_npv, -5])),
            (self.mon_npv, self.npv, LinearExpression([self.mon_npv, self.minus_npv])),
            (self.mon_npv, self.param, LinearExpression([self.mon_npv, -6])),
            (
                self.mon_npv,
                self.param_mut,
                LinearExpression([self.mon_npv, self.minus_param_mut]),
            ),
            # 8:
            (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.minus_var])),
            (
                self.mon_npv,
                self.mon_native,
                LinearExpression([self.mon_npv, self.minus_mon_native]),
            ),
            (
                self.mon_npv,
                self.mon_param,
                LinearExpression([self.mon_npv, self.minus_mon_param]),
            ),
            (
                self.mon_npv,
                self.mon_npv,
                LinearExpression([self.mon_npv, self.minus_mon_npv]),
            ),
            # 12:
            (
                self.mon_npv,
                self.linear,
                SumExpression([self.mon_npv, self.minus_linear]),
            ),
            (self.mon_npv, self.sum, SumExpression([self.mon_npv, self.minus_sum])),
            (self.mon_npv, self.other, SumExpression([self.mon_npv, self.minus_other])),
            (self.mon_npv, self.mutable_l0, self.mon_npv),
            # 16:
            (
                self.mon_npv,
                self.mutable_l1,
                LinearExpression([self.mon_npv, self.minus_mon_npv]),
            ),
            (
                self.mon_npv,
                self.mutable_l2,
                SumExpression([self.mon_npv, self.minus_l2]),
            ),
            (self.mon_npv, self.param0, self.mon_npv),
            (self.mon_npv, self.param1, LinearExpression([self.mon_npv, -1])),
            # 20:
            (
                self.mon_npv,
                self.mutable_l3,
                LinearExpression([self.mon_npv, self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_linear(self):
        tests = [
            (self.linear, self.invalid, NotImplemented),
            (
                self.linear,
                self.asbinary,
                LinearExpression(self.linear.args + [self.minus_bin]),
            ),
            (self.linear, self.zero, self.linear),
            (self.linear, self.one, LinearExpression(self.linear.args + [-1])),
            # 4:
            (self.linear, self.native, LinearExpression(self.linear.args + [-5])),
            (
                self.linear,
                self.npv,
                LinearExpression(self.linear.args + [self.minus_npv]),
            ),
            (self.linear, self.param, LinearExpression(self.linear.args + [-6])),
            (
                self.linear,
                self.param_mut,
                LinearExpression(self.linear.args + [self.minus_param_mut]),
            ),
            # 8:
            (
                self.linear,
                self.var,
                LinearExpression(self.linear.args + [self.minus_var]),
            ),
            (
                self.linear,
                self.mon_native,
                LinearExpression(self.linear.args + [self.minus_mon_native]),
            ),
            (
                self.linear,
                self.mon_param,
                LinearExpression(self.linear.args + [self.minus_mon_param]),
            ),
            (
                self.linear,
                self.mon_npv,
                LinearExpression(self.linear.args + [self.minus_mon_npv]),
            ),
            # 12:
            (self.linear, self.linear, SumExpression([self.linear, self.minus_linear])),
            (self.linear, self.sum, SumExpression([self.linear, self.minus_sum])),
            (self.linear, self.other, SumExpression([self.linear, self.minus_other])),
            (self.linear, self.mutable_l0, self.linear),
            # 16:
            (
                self.linear,
                self.mutable_l1,
                LinearExpression(self.linear.args + [self.minus_mon_npv]),
            ),
            (self.linear, self.mutable_l2, SumExpression([self.linear, self.minus_l2])),
            (self.linear, self.param0, self.linear),
            (self.linear, self.param1, LinearExpression(self.linear.args + [-1])),
            # 20:
            (
                self.linear,
                self.mutable_l3,
                LinearExpression(self.linear.args + [self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_sum(self):
        tests = [
            (self.sum, self.invalid, NotImplemented),
            (self.sum, self.asbinary, SumExpression(self.sum.args + [self.minus_bin])),
            (self.sum, self.zero, self.sum),
            (self.sum, self.one, SumExpression(self.sum.args + [-1])),
            # 4:
            (self.sum, self.native, SumExpression(self.sum.args + [-5])),
            (self.sum, self.npv, SumExpression(self.sum.args + [self.minus_npv])),
            (self.sum, self.param, SumExpression(self.sum.args + [-6])),
            (
                self.sum,
                self.param_mut,
                SumExpression(self.sum.args + [self.minus_param_mut]),
            ),
            # 8:
            (self.sum, self.var, SumExpression(self.sum.args + [self.minus_var])),
            (
                self.sum,
                self.mon_native,
                SumExpression(self.sum.args + [self.minus_mon_native]),
            ),
            (
                self.sum,
                self.mon_param,
                SumExpression(self.sum.args + [self.minus_mon_param]),
            ),
            (
                self.sum,
                self.mon_npv,
                SumExpression(self.sum.args + [self.minus_mon_npv]),
            ),
            # 12:
            (self.sum, self.linear, SumExpression(self.sum.args + [self.minus_linear])),
            (self.sum, self.sum, SumExpression(self.sum.args + [self.minus_sum])),
            (self.sum, self.other, SumExpression(self.sum.args + [self.minus_other])),
            (self.sum, self.mutable_l0, self.sum),
            # 16:
            (
                self.sum,
                self.mutable_l1,
                SumExpression(self.sum.args + [self.minus_mon_npv]),
            ),
            (self.sum, self.mutable_l2, SumExpression(self.sum.args + [self.minus_l2])),
            (self.sum, self.param0, self.sum),
            (self.sum, self.param1, SumExpression(self.sum.args + [-1])),
            # 20:
            (
                self.sum,
                self.mutable_l3,
                SumExpression(self.sum.args + [self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_other(self):
        tests = [
            (self.other, self.invalid, NotImplemented),
            (self.other, self.asbinary, SumExpression([self.other, self.minus_bin])),
            (self.other, self.zero, self.other),
            (self.other, self.one, SumExpression([self.other, -1])),
            # 4:
            (self.other, self.native, SumExpression([self.other, -5])),
            (self.other, self.npv, SumExpression([self.other, self.minus_npv])),
            (self.other, self.param, SumExpression([self.other, -6])),
            (
                self.other,
                self.param_mut,
                SumExpression([self.other, self.minus_param_mut]),
            ),
            # 8:
            (self.other, self.var, SumExpression([self.other, self.minus_var])),
            (
                self.other,
                self.mon_native,
                SumExpression([self.other, self.minus_mon_native]),
            ),
            (
                self.other,
                self.mon_param,
                SumExpression([self.other, self.minus_mon_param]),
            ),
            (self.other, self.mon_npv, SumExpression([self.other, self.minus_mon_npv])),
            # 12:
            (self.other, self.linear, SumExpression([self.other, self.minus_linear])),
            (self.other, self.sum, SumExpression([self.other, self.minus_sum])),
            (self.other, self.other, SumExpression([self.other, self.minus_other])),
            (self.other, self.mutable_l0, self.other),
            # 16:
            (
                self.other,
                self.mutable_l1,
                SumExpression([self.other, self.minus_mon_npv]),
            ),
            (self.other, self.mutable_l2, SumExpression([self.other, self.minus_l2])),
            (self.other, self.param0, self.other),
            (self.other, self.param1, SumExpression([self.other, -1])),
            # 20:
            (self.other, self.mutable_l3, SumExpression([self.other, self.minus_npv])),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_mutable_l0(self):
        tests = [
            (self.mutable_l0, self.invalid, NotImplemented),
            (self.mutable_l0, self.asbinary, self.minus_bin),
            (self.mutable_l0, self.zero, 0),
            (self.mutable_l0, self.one, -1),
            # 4:
            (self.mutable_l0, self.native, -5),
            (self.mutable_l0, self.npv, self.minus_npv),
            (self.mutable_l0, self.param, -6),
            (self.mutable_l0, self.param_mut, self.minus_param_mut),
            # 8:
            (self.mutable_l0, self.var, self.minus_var),
            (self.mutable_l0, self.mon_native, self.minus_mon_native),
            (self.mutable_l0, self.mon_param, self.minus_mon_param),
            (self.mutable_l0, self.mon_npv, self.minus_mon_npv),
            # 12:
            (self.mutable_l0, self.linear, self.minus_linear),
            (self.mutable_l0, self.sum, self.minus_sum),
            (self.mutable_l0, self.other, self.minus_other),
            (self.mutable_l0, self.mutable_l0, self.l0),
            # 16:
            (self.mutable_l0, self.mutable_l1, self.minus_mon_npv),
            (self.mutable_l0, self.mutable_l2, self.minus_l2),
            (self.mutable_l0, self.param0, 0),
            (self.mutable_l0, self.param1, -1),
            # 20:
            (self.mutable_l0, self.mutable_l3, self.minus_npv),
        ]
        self._run_cases(tests, operator.sub)
        # Mutable isub handled by separate tests
        # self._run_cases(tests, operator.isub)

    def test_sub_mutable_l1(self):
        tests = [
            (self.mutable_l1, self.invalid, NotImplemented),
            (
                self.mutable_l1,
                self.asbinary,
                LinearExpression([self.l1, self.minus_bin]),
            ),
            (self.mutable_l1, self.zero, self.mon_npv),
            (self.mutable_l1, self.one, LinearExpression([self.l1, -1])),
            # 4:
            (self.mutable_l1, self.native, LinearExpression([self.l1, -5])),
            (self.mutable_l1, self.npv, LinearExpression([self.l1, self.minus_npv])),
            (self.mutable_l1, self.param, LinearExpression([self.l1, -6])),
            (
                self.mutable_l1,
                self.param_mut,
                LinearExpression([self.l1, self.minus_param_mut]),
            ),
            # 8:
            (self.mutable_l1, self.var, LinearExpression([self.l1, self.minus_var])),
            (
                self.mutable_l1,
                self.mon_native,
                LinearExpression([self.l1, self.minus_mon_native]),
            ),
            (
                self.mutable_l1,
                self.mon_param,
                LinearExpression([self.l1, self.minus_mon_param]),
            ),
            (
                self.mutable_l1,
                self.mon_npv,
                LinearExpression([self.l1, self.minus_mon_npv]),
            ),
            # 12:
            (self.mutable_l1, self.linear, SumExpression([self.l1, self.minus_linear])),
            (self.mutable_l1, self.sum, SumExpression([self.l1, self.minus_sum])),
            (self.mutable_l1, self.other, SumExpression([self.l1, self.minus_other])),
            (self.mutable_l1, self.mutable_l0, self.mon_npv),
            # 16:
            (
                self.mutable_l1,
                self.mutable_l1,
                LinearExpression([self.l1, self.minus_mon_npv]),
            ),
            (self.mutable_l1, self.mutable_l2, SumExpression([self.l1, self.minus_l2])),
            (self.mutable_l1, self.param0, self.mon_npv),
            (self.mutable_l1, self.param1, LinearExpression([self.l1, -1])),
            # 20:
            (
                self.mutable_l1,
                self.mutable_l3,
                LinearExpression([self.l1, self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        # Mutable isub handled by separate tests
        # self._run_cases(tests, operator.isub)

    def test_sub_mutable_l2(self):
        tests = [
            (self.mutable_l2, self.invalid, NotImplemented),
            (
                self.mutable_l2,
                self.asbinary,
                SumExpression(self.l2.args + [self.minus_bin]),
            ),
            (self.mutable_l2, self.zero, self.l2),
            (self.mutable_l2, self.one, SumExpression(self.l2.args + [-1])),
            # 4:
            (self.mutable_l2, self.native, SumExpression(self.l2.args + [-5])),
            (self.mutable_l2, self.npv, SumExpression(self.l2.args + [self.minus_npv])),
            (self.mutable_l2, self.param, SumExpression(self.l2.args + [-6])),
            (
                self.mutable_l2,
                self.param_mut,
                SumExpression(self.l2.args + [self.minus_param_mut]),
            ),
            # 8:
            (self.mutable_l2, self.var, SumExpression(self.l2.args + [self.minus_var])),
            (
                self.mutable_l2,
                self.mon_native,
                SumExpression(self.l2.args + [self.minus_mon_native]),
            ),
            (
                self.mutable_l2,
                self.mon_param,
                SumExpression(self.l2.args + [self.minus_mon_param]),
            ),
            (
                self.mutable_l2,
                self.mon_npv,
                SumExpression(self.l2.args + [self.minus_mon_npv]),
            ),
            # 12:
            (
                self.mutable_l2,
                self.linear,
                SumExpression(self.l2.args + [self.minus_linear]),
            ),
            (self.mutable_l2, self.sum, SumExpression(self.l2.args + [self.minus_sum])),
            (
                self.mutable_l2,
                self.other,
                SumExpression(self.l2.args + [self.minus_other]),
            ),
            (self.mutable_l2, self.mutable_l0, self.l2),
            # 16:
            (
                self.mutable_l2,
                self.mutable_l1,
                SumExpression(self.l2.args + [self.minus_mon_npv]),
            ),
            (
                self.mutable_l2,
                self.mutable_l2,
                SumExpression(self.l2.args + [self.minus_l2]),
            ),
            (self.mutable_l2, self.param0, self.l2),
            (self.mutable_l2, self.param1, SumExpression(self.l2.args + [-1])),
            # 20:
            (
                self.mutable_l2,
                self.mutable_l3,
                SumExpression(self.l2.args + [self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        # Mutable isub handled by separate tests
        # self._run_cases(tests, operator.isub)

    def test_sub_param0(self):
        tests = [
            (self.param0, self.invalid, NotImplemented),
            (self.param0, self.asbinary, self.minus_bin),
            (self.param0, self.zero, 0),
            (self.param0, self.one, -1),
            # 4:
            (self.param0, self.native, -5),
            (self.param0, self.npv, self.minus_npv),
            (self.param0, self.param, -6),
            (self.param0, self.param_mut, self.minus_param_mut),
            # 8:
            (self.param0, self.var, self.minus_var),
            (self.param0, self.mon_native, self.minus_mon_native),
            (self.param0, self.mon_param, self.minus_mon_param),
            (self.param0, self.mon_npv, self.minus_mon_npv),
            # 12:
            (self.param0, self.linear, self.minus_linear),
            (self.param0, self.sum, self.minus_sum),
            (self.param0, self.other, self.minus_other),
            (self.param0, self.mutable_l0, 0),
            # 16:
            (self.param0, self.mutable_l1, self.minus_mon_npv),
            (self.param0, self.mutable_l2, self.minus_l2),
            (self.param0, self.param0, 0),
            (self.param0, self.param1, -1),
            # 20:
            (self.param0, self.mutable_l3, self.minus_npv),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_param1(self):
        tests = [
            (self.param1, self.invalid, NotImplemented),
            (self.param1, self.asbinary, LinearExpression([1, self.minus_bin])),
            (self.param1, self.zero, 1),
            (self.param1, self.one, 0),
            # 4:
            (self.param1, self.native, -4),
            (self.param1, self.npv, NPV_SumExpression([1, self.minus_npv])),
            (self.param1, self.param, -5),
            (self.param1, self.param_mut, NPV_SumExpression([1, self.minus_param_mut])),
            # 8:
            (self.param1, self.var, LinearExpression([1, self.minus_var])),
            (
                self.param1,
                self.mon_native,
                LinearExpression([1, self.minus_mon_native]),
            ),
            (self.param1, self.mon_param, LinearExpression([1, self.minus_mon_param])),
            (self.param1, self.mon_npv, LinearExpression([1, self.minus_mon_npv])),
            # 12:
            (self.param1, self.linear, SumExpression([1, self.minus_linear])),
            (self.param1, self.sum, SumExpression([1, self.minus_sum])),
            (self.param1, self.other, SumExpression([1, self.minus_other])),
            (self.param1, self.mutable_l0, 1),
            # 16:
            (self.param1, self.mutable_l1, LinearExpression([1, self.minus_mon_npv])),
            (self.param1, self.mutable_l2, SumExpression([1, self.minus_l2])),
            (self.param1, self.param0, 1),
            (self.param1, self.param1, 0),
            # 20:
            (self.param1, self.mutable_l3, NPV_SumExpression([1, self.minus_npv])),
        ]
        self._run_cases(tests, operator.sub)
        self._run_cases(tests, operator.isub)

    def test_sub_mutable_l3(self):
        tests = [
            (self.mutable_l3, self.invalid, NotImplemented),
            (
                self.mutable_l3,
                self.asbinary,
                LinearExpression([self.l3, self.minus_bin]),
            ),
            (self.mutable_l3, self.zero, self.npv),
            (self.mutable_l3, self.one, NPV_SumExpression([self.l3, -1])),
            # 4:
            (self.mutable_l3, self.native, NPV_SumExpression([self.l3, -5])),
            (self.mutable_l3, self.npv, NPV_SumExpression([self.l3, self.minus_npv])),
            (self.mutable_l3, self.param, NPV_SumExpression([self.l3, -6])),
            (
                self.mutable_l3,
                self.param_mut,
                NPV_SumExpression([self.l3, self.minus_param_mut]),
            ),
            # 8:
            (self.mutable_l3, self.var, LinearExpression([self.l3, self.minus_var])),
            (
                self.mutable_l3,
                self.mon_native,
                LinearExpression([self.l3, self.minus_mon_native]),
            ),
            (
                self.mutable_l3,
                self.mon_param,
                LinearExpression([self.l3, self.minus_mon_param]),
            ),
            (
                self.mutable_l3,
                self.mon_npv,
                LinearExpression([self.l3, self.minus_mon_npv]),
            ),
            # 12:
            (self.mutable_l3, self.linear, SumExpression([self.l3, self.minus_linear])),
            (self.mutable_l3, self.sum, SumExpression([self.l3, self.minus_sum])),
            (self.mutable_l3, self.other, SumExpression([self.l3, self.minus_other])),
            (self.mutable_l3, self.mutable_l0, self.npv),
            # 16:
            (
                self.mutable_l3,
                self.mutable_l1,
                LinearExpression([self.l3, self.minus_mon_npv]),
            ),
            (self.mutable_l3, self.mutable_l2, SumExpression([self.l3, self.minus_l2])),
            (self.mutable_l3, self.param0, self.npv),
            (self.mutable_l3, self.param1, NPV_SumExpression([self.l3, -1])),
            # 20:
            # Note that because the mutable is resolved to a NPV_Sum in
            # the negation, the 1-term summation for the first arg is
            # not resolved to a bare term
            (
                self.mutable_l3,
                self.mutable_l3,
                NPV_SumExpression([self.l3, self.minus_npv]),
            ),
        ]
        self._run_cases(tests, operator.sub)
        # Mutable isub handled by separate tests
        # self._run_cases(tests, operator.isub)

    #
    #
    # MULTIPLICATION
    #
    #

    def test_mul_invalid(self):
        tests = [
            (self.invalid, self.invalid, NotImplemented),
            (self.invalid, self.asbinary, NotImplemented),
            # "invalid(str) * {0, 1, native}" are legitimate Python
            # operations and should never hit the Pyomo expression
            # system
            (self.invalid, self.zero, self.SKIP),
            (self.invalid, self.one, self.SKIP),
            # 4:
            (self.invalid, self.native, self.SKIP),
            (self.invalid, self.npv, NotImplemented),
            (self.invalid, self.param, NotImplemented),
            (self.invalid, self.param_mut, NotImplemented),
            # 8:
            (self.invalid, self.var, NotImplemented),
            (self.invalid, self.mon_native, NotImplemented),
            (self.invalid, self.mon_param, NotImplemented),
            (self.invalid, self.mon_npv, NotImplemented),
            # 12:
            (self.invalid, self.linear, NotImplemented),
            (self.invalid, self.sum, NotImplemented),
            (self.invalid, self.other, NotImplemented),
            (self.invalid, self.mutable_l0, NotImplemented),
            # 16:
            (self.invalid, self.mutable_l1, NotImplemented),
            (self.invalid, self.mutable_l2, NotImplemented),
            (self.invalid, self.param0, NotImplemented),
            (self.invalid, self.param1, NotImplemented),
            # 20:
            (self.invalid, self.mutable_l3, NotImplemented),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_asbinary(self):
        tests = [
            (self.asbinary, self.invalid, NotImplemented),
            # BooleanVar objects do not support multiplication
            (self.asbinary, self.asbinary, NotImplemented),
            (self.asbinary, self.zero, MonomialTermExpression((0, self.bin))),
            (self.asbinary, self.one, self.bin),
            # 4:
            (self.asbinary, self.native, MonomialTermExpression((5, self.bin))),
            (self.asbinary, self.npv, MonomialTermExpression((self.npv, self.bin))),
            (self.asbinary, self.param, MonomialTermExpression((6, self.bin))),
            (
                self.asbinary,
                self.param_mut,
                MonomialTermExpression((self.param_mut, self.bin)),
            ),
            # 8:
            (self.asbinary, self.var, ProductExpression((self.bin, self.var))),
            (
                self.asbinary,
                self.mon_native,
                ProductExpression((self.bin, self.mon_native)),
            ),
            (
                self.asbinary,
                self.mon_param,
                ProductExpression((self.bin, self.mon_param)),
            ),
            (self.asbinary, self.mon_npv, ProductExpression((self.bin, self.mon_npv))),
            # 12:
            (self.asbinary, self.linear, ProductExpression((self.bin, self.linear))),
            (self.asbinary, self.sum, ProductExpression((self.bin, self.sum))),
            (self.asbinary, self.other, ProductExpression((self.bin, self.other))),
            (self.asbinary, self.mutable_l0, MonomialTermExpression((0, self.bin))),
            # 16:
            (
                self.asbinary,
                self.mutable_l1,
                ProductExpression((self.bin, self.mon_npv)),
            ),
            (self.asbinary, self.mutable_l2, ProductExpression((self.bin, self.l2))),
            (self.asbinary, self.param0, MonomialTermExpression((0, self.bin))),
            (self.asbinary, self.param1, self.bin),
            # 20:
            (
                self.asbinary,
                self.mutable_l3,
                MonomialTermExpression((self.npv, self.bin)),
            ),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_zero(self):
        tests = [
            # "Zero * invalid(str)" is a legitimate Python operation and
            # should never hit the Pyomo expression system
            (self.zero, self.invalid, self.SKIP),
            (self.zero, self.asbinary, MonomialTermExpression((0, self.bin))),
            (self.zero, self.zero, 0),
            (self.zero, self.one, 0),
            # 4:
            (self.zero, self.native, 0),
            (self.zero, self.npv, NPV_ProductExpression((0, self.npv))),
            (self.zero, self.param, 0),
            (self.zero, self.param_mut, NPV_ProductExpression((0, self.param_mut))),
            # 8:
            (self.zero, self.var, MonomialTermExpression((0, self.var))),
            (
                self.zero,
                self.mon_native,
                MonomialTermExpression((0, self.mon_native.arg(1))),
            ),
            (
                self.zero,
                self.mon_param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_param.arg(0))),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.zero,
                self.mon_npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 12:
            (self.zero, self.linear, ProductExpression((0, self.linear))),
            (self.zero, self.sum, ProductExpression((0, self.sum))),
            (self.zero, self.other, ProductExpression((0, self.other))),
            (self.zero, self.mutable_l0, 0),
            # 16:
            (
                self.zero,
                self.mutable_l1,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.zero, self.mutable_l2, ProductExpression((0, self.l2))),
            (self.zero, self.param0, 0),
            (self.zero, self.param1, 0),
            # 20:
            (self.zero, self.mutable_l3, NPV_ProductExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_one(self):
        tests = [
            # "One * invalid(str)" is a legitimate Python operation and
            # should never hit the Pyomo expression system
            (self.one, self.invalid, self.SKIP),
            (self.one, self.asbinary, self.bin),
            (self.one, self.zero, 0),
            (self.one, self.one, 1),
            # 4:
            (self.one, self.native, 5),
            (self.one, self.npv, self.npv),
            (self.one, self.param, self.param),
            (self.one, self.param_mut, self.param_mut),
            # 8:
            (self.one, self.var, self.var),
            (self.one, self.mon_native, self.mon_native),
            (self.one, self.mon_param, self.mon_param),
            (self.one, self.mon_npv, self.mon_npv),
            # 12:
            (self.one, self.linear, self.linear),
            (self.one, self.sum, self.sum),
            (self.one, self.other, self.other),
            (self.one, self.mutable_l0, 0),
            # 16:
            (self.one, self.mutable_l1, self.mon_npv),
            (self.one, self.mutable_l2, self.l2),
            (self.one, self.param0, self.param0),
            (self.one, self.param1, self.param1),
            # 20:
            (self.one, self.mutable_l3, self.npv),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_native(self):
        tests = [
            # "Native * invalid(str) is a legitimate Python operation and
            # should never hit the Pyomo expression system
            (self.native, self.invalid, self.SKIP),
            (self.native, self.asbinary, MonomialTermExpression((5, self.bin))),
            (self.native, self.zero, 0),
            (self.native, self.one, 5),
            # 4:
            (self.native, self.native, 25),
            (self.native, self.npv, NPV_ProductExpression((5, self.npv))),
            (self.native, self.param, 30),
            (self.native, self.param_mut, NPV_ProductExpression((5, self.param_mut))),
            # 8:
            (self.native, self.var, MonomialTermExpression((5, self.var))),
            (
                self.native,
                self.mon_native,
                MonomialTermExpression((15, self.mon_native.arg(1))),
            ),
            (
                self.native,
                self.mon_param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((5, self.mon_param.arg(0))),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.native,
                self.mon_npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((5, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 12:
            (self.native, self.linear, ProductExpression((5, self.linear))),
            (self.native, self.sum, ProductExpression((5, self.sum))),
            (self.native, self.other, ProductExpression((5, self.other))),
            (self.native, self.mutable_l0, 0),
            # 16:
            (
                self.native,
                self.mutable_l1,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((5, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.native, self.mutable_l2, ProductExpression((5, self.l2))),
            (self.native, self.param0, 0),
            (self.native, self.param1, 5),
            # 20:
            (self.native, self.mutable_l3, NPV_ProductExpression((5, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_npv(self):
        tests = [
            (self.npv, self.invalid, NotImplemented),
            (self.npv, self.asbinary, MonomialTermExpression((self.npv, self.bin))),
            (self.npv, self.zero, NPV_ProductExpression((self.npv, 0))),
            (self.npv, self.one, self.npv),
            # 4:
            (self.npv, self.native, NPV_ProductExpression((self.npv, 5))),
            (self.npv, self.npv, NPV_ProductExpression((self.npv, self.npv))),
            (self.npv, self.param, NPV_ProductExpression((self.npv, 6))),
            (
                self.npv,
                self.param_mut,
                NPV_ProductExpression((self.npv, self.param_mut)),
            ),
            # 8:
            (self.npv, self.var, MonomialTermExpression((self.npv, self.var))),
            (
                self.npv,
                self.mon_native,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.npv, self.mon_native.arg(0))),
                        self.mon_native.arg(1),
                    )
                ),
            ),
            (
                self.npv,
                self.mon_param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.npv, self.mon_param.arg(0))),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.npv,
                self.mon_npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.npv, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 12:
            (self.npv, self.linear, ProductExpression((self.npv, self.linear))),
            (self.npv, self.sum, ProductExpression((self.npv, self.sum))),
            (self.npv, self.other, ProductExpression((self.npv, self.other))),
            (self.npv, self.mutable_l0, NPV_ProductExpression((self.npv, 0))),
            # 16:
            (
                self.npv,
                self.mutable_l1,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.npv, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.npv, self.mutable_l2, ProductExpression((self.npv, self.l2))),
            (self.npv, self.param0, NPV_ProductExpression((self.npv, 0))),
            (self.npv, self.param1, self.npv),
            # 20:
            (self.npv, self.mutable_l3, NPV_ProductExpression((self.npv, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_param(self):
        tests = [
            (self.param, self.invalid, NotImplemented),
            (self.param, self.asbinary, MonomialTermExpression((6, self.bin))),
            (self.param, self.zero, 0),
            (self.param, self.one, 6),
            # 4:
            (self.param, self.native, 30),
            (self.param, self.npv, NPV_ProductExpression((6, self.npv))),
            (self.param, self.param, 36),
            (self.param, self.param_mut, NPV_ProductExpression((6, self.param_mut))),
            # 8:
            (self.param, self.var, MonomialTermExpression((6, self.var))),
            (
                self.param,
                self.mon_native,
                MonomialTermExpression((18, self.mon_native.arg(1))),
            ),
            (
                self.param,
                self.mon_param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((6, self.mon_param.arg(0))),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.param,
                self.mon_npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((6, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 12:
            (self.param, self.linear, ProductExpression((6, self.linear))),
            (self.param, self.sum, ProductExpression((6, self.sum))),
            (self.param, self.other, ProductExpression((6, self.other))),
            (self.param, self.mutable_l0, 0),
            # 16:
            (
                self.param,
                self.mutable_l1,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((6, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.param, self.mutable_l2, ProductExpression((6, self.l2))),
            (self.param, self.param0, 0),
            (self.param, self.param1, 6),
            # 20:
            (self.param, self.mutable_l3, NPV_ProductExpression((6, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_param_mut(self):
        tests = [
            (self.param_mut, self.invalid, NotImplemented),
            (
                self.param_mut,
                self.asbinary,
                MonomialTermExpression((self.param_mut, self.bin)),
            ),
            (self.param_mut, self.zero, NPV_ProductExpression((self.param_mut, 0))),
            (self.param_mut, self.one, self.param_mut),
            # 4:
            (self.param_mut, self.native, NPV_ProductExpression((self.param_mut, 5))),
            (
                self.param_mut,
                self.npv,
                NPV_ProductExpression((self.param_mut, self.npv)),
            ),
            (self.param_mut, self.param, NPV_ProductExpression((self.param_mut, 6))),
            (
                self.param_mut,
                self.param_mut,
                NPV_ProductExpression((self.param_mut, self.param_mut)),
            ),
            # 8:
            (
                self.param_mut,
                self.var,
                MonomialTermExpression((self.param_mut, self.var)),
            ),
            (
                self.param_mut,
                self.mon_native,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.param_mut, self.mon_native.arg(0))),
                        self.mon_native.arg(1),
                    )
                ),
            ),
            (
                self.param_mut,
                self.mon_param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.param_mut, self.mon_param.arg(0))),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.param_mut,
                self.mon_npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.param_mut, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 12:
            (
                self.param_mut,
                self.linear,
                ProductExpression((self.param_mut, self.linear)),
            ),
            (self.param_mut, self.sum, ProductExpression((self.param_mut, self.sum))),
            (
                self.param_mut,
                self.other,
                ProductExpression((self.param_mut, self.other)),
            ),
            (
                self.param_mut,
                self.mutable_l0,
                NPV_ProductExpression((self.param_mut, 0)),
            ),
            # 16:
            (
                self.param_mut,
                self.mutable_l1,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.param_mut, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.param_mut,
                self.mutable_l2,
                ProductExpression((self.param_mut, self.l2)),
            ),
            (self.param_mut, self.param0, NPV_ProductExpression((self.param_mut, 0))),
            (self.param_mut, self.param1, self.param_mut),
            # 20:
            (
                self.param_mut,
                self.mutable_l3,
                NPV_ProductExpression((self.param_mut, self.npv)),
            ),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_var(self):
        tests = [
            (self.var, self.invalid, NotImplemented),
            (self.var, self.asbinary, ProductExpression((self.var, self.bin))),
            (self.var, self.zero, MonomialTermExpression((0, self.var))),
            (self.var, self.one, self.var),
            # 4:
            (self.var, self.native, MonomialTermExpression((5, self.var))),
            (self.var, self.npv, MonomialTermExpression((self.npv, self.var))),
            (self.var, self.param, MonomialTermExpression((6, self.var))),
            (
                self.var,
                self.param_mut,
                MonomialTermExpression((self.param_mut, self.var)),
            ),
            # 8:
            (self.var, self.var, ProductExpression((self.var, self.var))),
            (self.var, self.mon_native, ProductExpression((self.var, self.mon_native))),
            (self.var, self.mon_param, ProductExpression((self.var, self.mon_param))),
            (self.var, self.mon_npv, ProductExpression((self.var, self.mon_npv))),
            # 12:
            (self.var, self.linear, ProductExpression((self.var, self.linear))),
            (self.var, self.sum, ProductExpression((self.var, self.sum))),
            (self.var, self.other, ProductExpression((self.var, self.other))),
            (self.var, self.mutable_l0, MonomialTermExpression((0, self.var))),
            # 16:
            (self.var, self.mutable_l1, ProductExpression((self.var, self.mon_npv))),
            (self.var, self.mutable_l2, ProductExpression((self.var, self.l2))),
            (self.var, self.param0, MonomialTermExpression((0, self.var))),
            (self.var, self.param1, self.var),
            # 20:
            (self.var, self.mutable_l3, MonomialTermExpression((self.npv, self.var))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_mon_native(self):
        tests = [
            (self.mon_native, self.invalid, NotImplemented),
            (
                self.mon_native,
                self.asbinary,
                ProductExpression((self.mon_native, self.bin)),
            ),
            (
                self.mon_native,
                self.zero,
                MonomialTermExpression((0, self.mon_native.arg(1))),
            ),
            (self.mon_native, self.one, self.mon_native),
            # 4:
            (
                self.mon_native,
                self.native,
                MonomialTermExpression((15, self.mon_native.arg(1))),
            ),
            (
                self.mon_native,
                self.npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_native.arg(0), self.npv)),
                        self.mon_native.arg(1),
                    )
                ),
            ),
            (
                self.mon_native,
                self.param,
                MonomialTermExpression((18, self.mon_native.arg(1))),
            ),
            (
                self.mon_native,
                self.param_mut,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_native.arg(0), self.param_mut)),
                        self.mon_native.arg(1),
                    )
                ),
            ),
            # 8:
            (self.mon_native, self.var, ProductExpression((self.mon_native, self.var))),
            (
                self.mon_native,
                self.mon_native,
                ProductExpression((self.mon_native, self.mon_native)),
            ),
            (
                self.mon_native,
                self.mon_param,
                ProductExpression((self.mon_native, self.mon_param)),
            ),
            (
                self.mon_native,
                self.mon_npv,
                ProductExpression((self.mon_native, self.mon_npv)),
            ),
            # 12:
            (
                self.mon_native,
                self.linear,
                ProductExpression((self.mon_native, self.linear)),
            ),
            (self.mon_native, self.sum, ProductExpression((self.mon_native, self.sum))),
            (
                self.mon_native,
                self.other,
                ProductExpression((self.mon_native, self.other)),
            ),
            (
                self.mon_native,
                self.mutable_l0,
                MonomialTermExpression((0, self.mon_native.arg(1))),
            ),
            # 16:
            (
                self.mon_native,
                self.mutable_l1,
                ProductExpression((self.mon_native, self.mon_npv)),
            ),
            (
                self.mon_native,
                self.mutable_l2,
                ProductExpression((self.mon_native, self.l2)),
            ),
            (
                self.mon_native,
                self.param0,
                MonomialTermExpression((0, self.mon_native.arg(1))),
            ),
            (self.mon_native, self.param1, self.mon_native),
            # 20:
            (
                self.mon_native,
                self.mutable_l3,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_native.arg(0), self.npv)),
                        self.mon_native.arg(1),
                    )
                ),
            ),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_mon_param(self):
        tests = [
            (self.mon_param, self.invalid, NotImplemented),
            (
                self.mon_param,
                self.asbinary,
                ProductExpression((self.mon_param, self.bin)),
            ),
            (
                self.mon_param,
                self.zero,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_param.arg(0), 0)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (self.mon_param, self.one, self.mon_param),
            # 4:
            (
                self.mon_param,
                self.native,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_param.arg(0), 5)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.mon_param,
                self.npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_param.arg(0), self.npv)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.mon_param,
                self.param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_param.arg(0), 6)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.mon_param,
                self.param_mut,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_param.arg(0), self.param_mut)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            # 8:
            (self.mon_param, self.var, ProductExpression((self.mon_param, self.var))),
            (
                self.mon_param,
                self.mon_native,
                ProductExpression((self.mon_param, self.mon_native)),
            ),
            (
                self.mon_param,
                self.mon_param,
                ProductExpression((self.mon_param, self.mon_param)),
            ),
            (
                self.mon_param,
                self.mon_npv,
                ProductExpression((self.mon_param, self.mon_npv)),
            ),
            # 12:
            (
                self.mon_param,
                self.linear,
                ProductExpression((self.mon_param, self.linear)),
            ),
            (self.mon_param, self.sum, ProductExpression((self.mon_param, self.sum))),
            (
                self.mon_param,
                self.other,
                ProductExpression((self.mon_param, self.other)),
            ),
            (
                self.mon_param,
                self.mutable_l0,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_param.arg(0), 0)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            # 16:
            (
                self.mon_param,
                self.mutable_l1,
                ProductExpression((self.mon_param, self.mon_npv)),
            ),
            (
                self.mon_param,
                self.mutable_l2,
                ProductExpression((self.mon_param, self.l2)),
            ),
            (
                self.mon_param,
                self.param0,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_param.arg(0), 0)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (self.mon_param, self.param1, self.mon_param),
            # 20:
            (
                self.mon_param,
                self.mutable_l3,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_param.arg(0), self.npv)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_mon_npv(self):
        tests = [
            (self.mon_npv, self.invalid, NotImplemented),
            (self.mon_npv, self.asbinary, ProductExpression((self.mon_npv, self.bin))),
            (
                self.mon_npv,
                self.zero,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 0)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.mon_npv, self.one, self.mon_npv),
            # 4:
            (
                self.mon_npv,
                self.native,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 5)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mon_npv,
                self.npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), self.npv)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mon_npv,
                self.param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 6)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mon_npv,
                self.param_mut,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), self.param_mut)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 8:
            (self.mon_npv, self.var, ProductExpression((self.mon_npv, self.var))),
            (
                self.mon_npv,
                self.mon_native,
                ProductExpression((self.mon_npv, self.mon_native)),
            ),
            (
                self.mon_npv,
                self.mon_param,
                ProductExpression((self.mon_npv, self.mon_param)),
            ),
            (
                self.mon_npv,
                self.mon_npv,
                ProductExpression((self.mon_npv, self.mon_npv)),
            ),
            # 12:
            (self.mon_npv, self.linear, ProductExpression((self.mon_npv, self.linear))),
            (self.mon_npv, self.sum, ProductExpression((self.mon_npv, self.sum))),
            (self.mon_npv, self.other, ProductExpression((self.mon_npv, self.other))),
            (
                self.mon_npv,
                self.mutable_l0,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 0)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 16:
            (
                self.mon_npv,
                self.mutable_l1,
                ProductExpression((self.mon_npv, self.mon_npv)),
            ),
            (self.mon_npv, self.mutable_l2, ProductExpression((self.mon_npv, self.l2))),
            (
                self.mon_npv,
                self.param0,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 0)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.mon_npv, self.param1, self.mon_npv),
            # 20:
            (
                self.mon_npv,
                self.mutable_l3,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), self.npv)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_linear(self):
        tests = [
            (self.linear, self.invalid, NotImplemented),
            (self.linear, self.asbinary, ProductExpression((self.linear, self.bin))),
            (self.linear, self.zero, ProductExpression((self.linear, 0))),
            (self.linear, self.one, self.linear),
            # 4:
            (self.linear, self.native, ProductExpression((self.linear, 5))),
            (self.linear, self.npv, ProductExpression((self.linear, self.npv))),
            (self.linear, self.param, ProductExpression((self.linear, 6))),
            (
                self.linear,
                self.param_mut,
                ProductExpression((self.linear, self.param_mut)),
            ),
            # 8:
            (self.linear, self.var, ProductExpression((self.linear, self.var))),
            (
                self.linear,
                self.mon_native,
                ProductExpression((self.linear, self.mon_native)),
            ),
            (
                self.linear,
                self.mon_param,
                ProductExpression((self.linear, self.mon_param)),
            ),
            (self.linear, self.mon_npv, ProductExpression((self.linear, self.mon_npv))),
            # 12:
            (self.linear, self.linear, ProductExpression((self.linear, self.linear))),
            (self.linear, self.sum, ProductExpression((self.linear, self.sum))),
            (self.linear, self.other, ProductExpression((self.linear, self.other))),
            (self.linear, self.mutable_l0, ProductExpression((self.linear, 0))),
            # 16:
            (
                self.linear,
                self.mutable_l1,
                ProductExpression((self.linear, self.mon_npv)),
            ),
            (self.linear, self.mutable_l2, ProductExpression((self.linear, self.l2))),
            (self.linear, self.param0, ProductExpression((self.linear, 0))),
            (self.linear, self.param1, self.linear),
            # 20:
            (self.linear, self.mutable_l3, ProductExpression((self.linear, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_sum(self):
        tests = [
            (self.sum, self.invalid, NotImplemented),
            (self.sum, self.asbinary, ProductExpression((self.sum, self.bin))),
            (self.sum, self.zero, ProductExpression((self.sum, 0))),
            (self.sum, self.one, self.sum),
            # 4:
            (self.sum, self.native, ProductExpression((self.sum, 5))),
            (self.sum, self.npv, ProductExpression((self.sum, self.npv))),
            (self.sum, self.param, ProductExpression((self.sum, 6))),
            (self.sum, self.param_mut, ProductExpression((self.sum, self.param_mut))),
            # 8:
            (self.sum, self.var, ProductExpression((self.sum, self.var))),
            (self.sum, self.mon_native, ProductExpression((self.sum, self.mon_native))),
            (self.sum, self.mon_param, ProductExpression((self.sum, self.mon_param))),
            (self.sum, self.mon_npv, ProductExpression((self.sum, self.mon_npv))),
            # 12:
            (self.sum, self.linear, ProductExpression((self.sum, self.linear))),
            (self.sum, self.sum, ProductExpression((self.sum, self.sum))),
            (self.sum, self.other, ProductExpression((self.sum, self.other))),
            (self.sum, self.mutable_l0, ProductExpression((self.sum, 0))),
            # 16:
            (self.sum, self.mutable_l1, ProductExpression((self.sum, self.mon_npv))),
            (self.sum, self.mutable_l2, ProductExpression((self.sum, self.l2))),
            (self.sum, self.param0, ProductExpression((self.sum, 0))),
            (self.sum, self.param1, self.sum),
            # 20:
            (self.sum, self.mutable_l3, ProductExpression((self.sum, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_other(self):
        tests = [
            (self.other, self.invalid, NotImplemented),
            (self.other, self.asbinary, ProductExpression((self.other, self.bin))),
            (self.other, self.zero, ProductExpression((self.other, 0))),
            (self.other, self.one, self.other),
            # 4:
            (self.other, self.native, ProductExpression((self.other, 5))),
            (self.other, self.npv, ProductExpression((self.other, self.npv))),
            (self.other, self.param, ProductExpression((self.other, 6))),
            (
                self.other,
                self.param_mut,
                ProductExpression((self.other, self.param_mut)),
            ),
            # 8:
            (self.other, self.var, ProductExpression((self.other, self.var))),
            (
                self.other,
                self.mon_native,
                ProductExpression((self.other, self.mon_native)),
            ),
            (
                self.other,
                self.mon_param,
                ProductExpression((self.other, self.mon_param)),
            ),
            (self.other, self.mon_npv, ProductExpression((self.other, self.mon_npv))),
            # 12:
            (self.other, self.linear, ProductExpression((self.other, self.linear))),
            (self.other, self.sum, ProductExpression((self.other, self.sum))),
            (self.other, self.other, ProductExpression((self.other, self.other))),
            (self.other, self.mutable_l0, ProductExpression((self.other, 0))),
            # 16:
            (
                self.other,
                self.mutable_l1,
                ProductExpression((self.other, self.mon_npv)),
            ),
            (self.other, self.mutable_l2, ProductExpression((self.other, self.l2))),
            (self.other, self.param0, ProductExpression((self.other, 0))),
            (self.other, self.param1, self.other),
            # 20:
            (self.other, self.mutable_l3, ProductExpression((self.other, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_mutable_l0(self):
        tests = [
            (self.mutable_l0, self.invalid, NotImplemented),
            (self.mutable_l0, self.asbinary, MonomialTermExpression((0, self.bin))),
            (self.mutable_l0, self.zero, 0),
            (self.mutable_l0, self.one, 0),
            # 4:
            (self.mutable_l0, self.native, 0),
            (self.mutable_l0, self.npv, NPV_ProductExpression((0, self.npv))),
            (self.mutable_l0, self.param, 0),
            (
                self.mutable_l0,
                self.param_mut,
                NPV_ProductExpression((0, self.param_mut)),
            ),
            # 8:
            (self.mutable_l0, self.var, MonomialTermExpression((0, self.var))),
            (
                self.mutable_l0,
                self.mon_native,
                MonomialTermExpression((0, self.mon_native.arg(1))),
            ),
            (
                self.mutable_l0,
                self.mon_param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_param.arg(0))),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l0,
                self.mon_npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 12:
            (self.mutable_l0, self.linear, ProductExpression((0, self.linear))),
            (self.mutable_l0, self.sum, ProductExpression((0, self.sum))),
            (self.mutable_l0, self.other, ProductExpression((0, self.other))),
            (self.mutable_l0, self.mutable_l0, 0),
            # 16:
            (
                self.mutable_l0,
                self.mutable_l1,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.mutable_l0, self.mutable_l2, ProductExpression((0, self.l2))),
            (self.mutable_l0, self.param0, 0),
            (self.mutable_l0, self.param1, 0),
            # 20:
            (self.mutable_l0, self.mutable_l3, NPV_ProductExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_mutable_l1(self):
        tests = [
            (self.mutable_l1, self.invalid, NotImplemented),
            (
                self.mutable_l1,
                self.asbinary,
                ProductExpression((self.mon_npv, self.bin)),
            ),
            (
                self.mutable_l1,
                self.zero,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 0)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.mutable_l1, self.one, self.mon_npv),
            # 4:
            (
                self.mutable_l1,
                self.native,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 5)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l1,
                self.npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), self.npv)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l1,
                self.param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 6)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l1,
                self.param_mut,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), self.param_mut)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 8:
            (self.mutable_l1, self.var, ProductExpression((self.mon_npv, self.var))),
            (
                self.mutable_l1,
                self.mon_native,
                ProductExpression((self.mon_npv, self.mon_native)),
            ),
            (
                self.mutable_l1,
                self.mon_param,
                ProductExpression((self.mon_npv, self.mon_param)),
            ),
            (
                self.mutable_l1,
                self.mon_npv,
                ProductExpression((self.mon_npv, self.mon_npv)),
            ),
            # 12:
            (
                self.mutable_l1,
                self.linear,
                ProductExpression((self.mon_npv, self.linear)),
            ),
            (self.mutable_l1, self.sum, ProductExpression((self.mon_npv, self.sum))),
            (
                self.mutable_l1,
                self.other,
                ProductExpression((self.mon_npv, self.other)),
            ),
            (
                self.mutable_l1,
                self.mutable_l0,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 0)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 16:
            (
                self.mutable_l1,
                self.mutable_l1,
                ProductExpression((self.mon_npv, self.mon_npv)),
            ),
            (
                self.mutable_l1,
                self.mutable_l2,
                ProductExpression((self.mon_npv, self.l2)),
            ),
            (
                self.mutable_l1,
                self.param0,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), 0)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.mutable_l1, self.param1, self.mon_npv),
            # 20:
            (
                self.mutable_l1,
                self.mutable_l3,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.mon_npv.arg(0), self.npv)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_mutable_l2(self):
        tests = [
            (self.mutable_l2, self.invalid, NotImplemented),
            (self.mutable_l2, self.asbinary, ProductExpression((self.l2, self.bin))),
            (self.mutable_l2, self.zero, ProductExpression((self.l2, 0))),
            (self.mutable_l2, self.one, self.l2),
            # 4:
            (self.mutable_l2, self.native, ProductExpression((self.l2, 5))),
            (self.mutable_l2, self.npv, ProductExpression((self.l2, self.npv))),
            (self.mutable_l2, self.param, ProductExpression((self.l2, 6))),
            (
                self.mutable_l2,
                self.param_mut,
                ProductExpression((self.l2, self.param_mut)),
            ),
            # 8:
            (self.mutable_l2, self.var, ProductExpression((self.l2, self.var))),
            (
                self.mutable_l2,
                self.mon_native,
                ProductExpression((self.l2, self.mon_native)),
            ),
            (
                self.mutable_l2,
                self.mon_param,
                ProductExpression((self.l2, self.mon_param)),
            ),
            (self.mutable_l2, self.mon_npv, ProductExpression((self.l2, self.mon_npv))),
            # 12:
            (self.mutable_l2, self.linear, ProductExpression((self.l2, self.linear))),
            (self.mutable_l2, self.sum, ProductExpression((self.l2, self.sum))),
            (self.mutable_l2, self.other, ProductExpression((self.l2, self.other))),
            (self.mutable_l2, self.mutable_l0, ProductExpression((self.l2, 0))),
            # 16:
            (
                self.mutable_l2,
                self.mutable_l1,
                ProductExpression((self.l2, self.mon_npv)),
            ),
            (self.mutable_l2, self.mutable_l2, ProductExpression((self.l2, self.l2))),
            (self.mutable_l2, self.param0, ProductExpression((self.l2, 0))),
            (self.mutable_l2, self.param1, self.l2),
            # 20:
            (self.mutable_l2, self.mutable_l3, ProductExpression((self.l2, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_param0(self):
        tests = [
            # "Param0 * invalid(str)" is a legitimate Python operation and
            # should never hit the Pyomo expression system
            (self.param0, self.invalid, self.SKIP),
            (self.param0, self.asbinary, MonomialTermExpression((0, self.bin))),
            (self.param0, self.zero, 0),
            (self.param0, self.one, 0),
            # 4:
            (self.param0, self.native, 0),
            (self.param0, self.npv, NPV_ProductExpression((0, self.npv))),
            (self.param0, self.param, 0),
            (self.param0, self.param_mut, NPV_ProductExpression((0, self.param_mut))),
            # 8:
            (self.param0, self.var, MonomialTermExpression((0, self.var))),
            (
                self.param0,
                self.mon_native,
                MonomialTermExpression((0, self.mon_native.arg(1))),
            ),
            (
                self.param0,
                self.mon_param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_param.arg(0))),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.param0,
                self.mon_npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 12:
            (self.param0, self.linear, ProductExpression((0, self.linear))),
            (self.param0, self.sum, ProductExpression((0, self.sum))),
            (self.param0, self.other, ProductExpression((0, self.other))),
            (self.param0, self.mutable_l0, 0),
            # 16:
            (
                self.param0,
                self.mutable_l1,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((0, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.param0, self.mutable_l2, ProductExpression((0, self.l2))),
            (self.param0, self.param0, 0),
            (self.param0, self.param1, 0),
            # 20:
            (self.param0, self.mutable_l3, NPV_ProductExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_param1(self):
        tests = [
            # "One * invalid(str)" is a legitimate Python operation and
            # should never hit the Pyomo expression system
            (self.param1, self.invalid, self.SKIP),
            (self.param1, self.asbinary, self.bin),
            (self.param1, self.zero, 0),
            (self.param1, self.one, 1),
            # 4:
            (self.param1, self.native, 5),
            (self.param1, self.npv, self.npv),
            (self.param1, self.param, self.param),
            (self.param1, self.param_mut, self.param_mut),
            # 8:
            (self.param1, self.var, self.var),
            (self.param1, self.mon_native, self.mon_native),
            (self.param1, self.mon_param, self.mon_param),
            (self.param1, self.mon_npv, self.mon_npv),
            # 12:
            (self.param1, self.linear, self.linear),
            (self.param1, self.sum, self.sum),
            (self.param1, self.other, self.other),
            (self.param1, self.mutable_l0, 0),
            # 16:
            (self.param1, self.mutable_l1, self.mon_npv),
            (self.param1, self.mutable_l2, self.l2),
            (self.param1, self.param0, self.param0),
            (self.param1, self.param1, self.param1),
            # 20:
            (self.param1, self.mutable_l3, self.npv),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    def test_mul_mutable_l3(self):
        tests = [
            (self.mutable_l3, self.invalid, NotImplemented),
            (
                self.mutable_l3,
                self.asbinary,
                MonomialTermExpression((self.npv, self.bin)),
            ),
            (self.mutable_l3, self.zero, NPV_ProductExpression((self.npv, 0))),
            (self.mutable_l3, self.one, self.npv),
            # 4:
            (self.mutable_l3, self.native, NPV_ProductExpression((self.npv, 5))),
            (self.mutable_l3, self.npv, NPV_ProductExpression((self.npv, self.npv))),
            (self.mutable_l3, self.param, NPV_ProductExpression((self.npv, 6))),
            (
                self.mutable_l3,
                self.param_mut,
                NPV_ProductExpression((self.npv, self.param_mut)),
            ),
            # 8:
            (self.mutable_l3, self.var, MonomialTermExpression((self.npv, self.var))),
            (
                self.mutable_l3,
                self.mon_native,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.npv, self.mon_native.arg(0))),
                        self.mon_native.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l3,
                self.mon_param,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.npv, self.mon_param.arg(0))),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l3,
                self.mon_npv,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.npv, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 12:
            (self.mutable_l3, self.linear, ProductExpression((self.npv, self.linear))),
            (self.mutable_l3, self.sum, ProductExpression((self.npv, self.sum))),
            (self.mutable_l3, self.other, ProductExpression((self.npv, self.other))),
            (self.mutable_l3, self.mutable_l0, NPV_ProductExpression((self.npv, 0))),
            # 16:
            (
                self.mutable_l3,
                self.mutable_l1,
                MonomialTermExpression(
                    (
                        NPV_ProductExpression((self.npv, self.mon_npv.arg(0))),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (self.mutable_l3, self.mutable_l2, ProductExpression((self.npv, self.l2))),
            (self.mutable_l3, self.param0, NPV_ProductExpression((self.npv, 0))),
            (self.mutable_l3, self.param1, self.npv),
            # 20:
            (
                self.mutable_l3,
                self.mutable_l3,
                NPV_ProductExpression((self.npv, self.npv)),
            ),
        ]
        self._run_cases(tests, operator.mul)
        self._run_cases(tests, operator.imul)

    #
    #
    # DIVISION
    #
    #

    def test_div_invalid(self):
        tests = [
            (self.invalid, self.invalid, NotImplemented),
            (self.invalid, self.asbinary, NotImplemented),
            (self.invalid, self.zero, NotImplemented),
            (self.invalid, self.one, NotImplemented),
            # 4:
            (self.invalid, self.native, NotImplemented),
            (self.invalid, self.npv, NotImplemented),
            (self.invalid, self.param, NotImplemented),
            (self.invalid, self.param_mut, NotImplemented),
            # 8:
            (self.invalid, self.var, NotImplemented),
            (self.invalid, self.mon_native, NotImplemented),
            (self.invalid, self.mon_param, NotImplemented),
            (self.invalid, self.mon_npv, NotImplemented),
            # 12:
            (self.invalid, self.linear, NotImplemented),
            (self.invalid, self.sum, NotImplemented),
            (self.invalid, self.other, NotImplemented),
            (self.invalid, self.mutable_l0, NotImplemented),
            # 16:
            (self.invalid, self.mutable_l1, NotImplemented),
            (self.invalid, self.mutable_l2, NotImplemented),
            (self.invalid, self.param0, NotImplemented),
            (self.invalid, self.param1, NotImplemented),
            # 20:
            (self.invalid, self.mutable_l3, NotImplemented),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_asbinary(self):
        tests = [
            (self.asbinary, self.invalid, NotImplemented),
            # BooleanVar objects do not support division
            (self.asbinary, self.asbinary, NotImplemented),
            (self.asbinary, self.zero, ZeroDivisionError),
            (self.asbinary, self.one, self.bin),
            # 4:
            (self.asbinary, self.native, MonomialTermExpression((0.2, self.bin))),
            (
                self.asbinary,
                self.npv,
                MonomialTermExpression(
                    (NPV_DivisionExpression((1, self.npv)), self.bin)
                ),
            ),
            (self.asbinary, self.param, MonomialTermExpression((1 / 6, self.bin))),
            (
                self.asbinary,
                self.param_mut,
                MonomialTermExpression(
                    (NPV_DivisionExpression((1, self.param_mut)), self.bin)
                ),
            ),
            # 8:
            (self.asbinary, self.var, DivisionExpression((self.bin, self.var))),
            (
                self.asbinary,
                self.mon_native,
                DivisionExpression((self.bin, self.mon_native)),
            ),
            (
                self.asbinary,
                self.mon_param,
                DivisionExpression((self.bin, self.mon_param)),
            ),
            (self.asbinary, self.mon_npv, DivisionExpression((self.bin, self.mon_npv))),
            # 12:
            (self.asbinary, self.linear, DivisionExpression((self.bin, self.linear))),
            (self.asbinary, self.sum, DivisionExpression((self.bin, self.sum))),
            (self.asbinary, self.other, DivisionExpression((self.bin, self.other))),
            (self.asbinary, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.asbinary,
                self.mutable_l1,
                DivisionExpression((self.bin, self.mon_npv)),
            ),
            (self.asbinary, self.mutable_l2, DivisionExpression((self.bin, self.l2))),
            (self.asbinary, self.param0, ZeroDivisionError),
            (self.asbinary, self.param1, self.bin),
            # 20:
            (
                self.asbinary,
                self.mutable_l3,
                MonomialTermExpression(
                    (NPV_DivisionExpression((1, self.npv)), self.bin)
                ),
            ),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_zero(self):
        tests = [
            (self.zero, self.invalid, NotImplemented),
            (self.zero, self.asbinary, DivisionExpression((0, self.bin))),
            (self.zero, self.zero, ZeroDivisionError),
            (self.zero, self.one, 0.0),
            # 4:
            (self.zero, self.native, 0.0),
            (self.zero, self.npv, NPV_DivisionExpression((0, self.npv))),
            (self.zero, self.param, 0.0),
            (self.zero, self.param_mut, NPV_DivisionExpression((0, self.param_mut))),
            # 8:
            (self.zero, self.var, DivisionExpression((0, self.var))),
            (self.zero, self.mon_native, DivisionExpression((0, self.mon_native))),
            (self.zero, self.mon_param, DivisionExpression((0, self.mon_param))),
            (self.zero, self.mon_npv, DivisionExpression((0, self.mon_npv))),
            # 12:
            (self.zero, self.linear, DivisionExpression((0, self.linear))),
            (self.zero, self.sum, DivisionExpression((0, self.sum))),
            (self.zero, self.other, DivisionExpression((0, self.other))),
            (self.zero, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.zero, self.mutable_l1, DivisionExpression((0, self.mon_npv))),
            (self.zero, self.mutable_l2, DivisionExpression((0, self.l2))),
            (self.zero, self.param0, ZeroDivisionError),
            (self.zero, self.param1, 0.0),
            # 20:
            (self.zero, self.mutable_l3, NPV_DivisionExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_one(self):
        tests = [
            (self.one, self.invalid, NotImplemented),
            (self.one, self.asbinary, DivisionExpression((1, self.bin))),
            (self.one, self.zero, ZeroDivisionError),
            (self.one, self.one, 1.0),
            # 4:
            (self.one, self.native, 0.2),
            (self.one, self.npv, NPV_DivisionExpression((1, self.npv))),
            (self.one, self.param, 1 / 6),
            (self.one, self.param_mut, NPV_DivisionExpression((1, self.param_mut))),
            # 8:
            (self.one, self.var, DivisionExpression((1, self.var))),
            (self.one, self.mon_native, DivisionExpression((1, self.mon_native))),
            (self.one, self.mon_param, DivisionExpression((1, self.mon_param))),
            (self.one, self.mon_npv, DivisionExpression((1, self.mon_npv))),
            # 12:
            (self.one, self.linear, DivisionExpression((1, self.linear))),
            (self.one, self.sum, DivisionExpression((1, self.sum))),
            (self.one, self.other, DivisionExpression((1, self.other))),
            (self.one, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.one, self.mutable_l1, DivisionExpression((1, self.mon_npv))),
            (self.one, self.mutable_l2, DivisionExpression((1, self.l2))),
            (self.one, self.param0, ZeroDivisionError),
            (self.one, self.param1, 1.0),
            # 20:
            (self.one, self.mutable_l3, NPV_DivisionExpression((1, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_native(self):
        tests = [
            (self.native, self.invalid, NotImplemented),
            (self.native, self.asbinary, DivisionExpression((5, self.bin))),
            (self.native, self.zero, ZeroDivisionError),
            (self.native, self.one, 5.0),
            # 4:
            (self.native, self.native, 1.0),
            (self.native, self.npv, NPV_DivisionExpression((5, self.npv))),
            (self.native, self.param, 5 / 6),
            (self.native, self.param_mut, NPV_DivisionExpression((5, self.param_mut))),
            # 8:
            (self.native, self.var, DivisionExpression((5, self.var))),
            (self.native, self.mon_native, DivisionExpression((5, self.mon_native))),
            (self.native, self.mon_param, DivisionExpression((5, self.mon_param))),
            (self.native, self.mon_npv, DivisionExpression((5, self.mon_npv))),
            # 12:
            (self.native, self.linear, DivisionExpression((5, self.linear))),
            (self.native, self.sum, DivisionExpression((5, self.sum))),
            (self.native, self.other, DivisionExpression((5, self.other))),
            (self.native, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.native, self.mutable_l1, DivisionExpression((5, self.mon_npv))),
            (self.native, self.mutable_l2, DivisionExpression((5, self.l2))),
            (self.native, self.param0, ZeroDivisionError),
            (self.native, self.param1, 5.0),
            # 20:
            (self.native, self.mutable_l3, NPV_DivisionExpression((5, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_npv(self):
        tests = [
            (self.npv, self.invalid, NotImplemented),
            (self.npv, self.asbinary, DivisionExpression((self.npv, self.bin))),
            (self.npv, self.zero, ZeroDivisionError),
            (self.npv, self.one, self.npv),
            # 4:
            (self.npv, self.native, NPV_DivisionExpression((self.npv, 5))),
            (self.npv, self.npv, NPV_DivisionExpression((self.npv, self.npv))),
            (self.npv, self.param, NPV_DivisionExpression((self.npv, 6))),
            (
                self.npv,
                self.param_mut,
                NPV_DivisionExpression((self.npv, self.param_mut)),
            ),
            # 8:
            (self.npv, self.var, DivisionExpression((self.npv, self.var))),
            (
                self.npv,
                self.mon_native,
                DivisionExpression((self.npv, self.mon_native)),
            ),
            (self.npv, self.mon_param, DivisionExpression((self.npv, self.mon_param))),
            (self.npv, self.mon_npv, DivisionExpression((self.npv, self.mon_npv))),
            # 12:
            (self.npv, self.linear, DivisionExpression((self.npv, self.linear))),
            (self.npv, self.sum, DivisionExpression((self.npv, self.sum))),
            (self.npv, self.other, DivisionExpression((self.npv, self.other))),
            (self.npv, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.npv, self.mutable_l1, DivisionExpression((self.npv, self.mon_npv))),
            (self.npv, self.mutable_l2, DivisionExpression((self.npv, self.l2))),
            (self.npv, self.param0, ZeroDivisionError),
            (self.npv, self.param1, self.npv),
            # 20:
            (self.npv, self.mutable_l3, NPV_DivisionExpression((self.npv, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_param(self):
        tests = [
            (self.param, self.invalid, NotImplemented),
            (self.param, self.asbinary, DivisionExpression((6, self.bin))),
            (self.param, self.zero, ZeroDivisionError),
            (self.param, self.one, 6.0),
            # 4:
            (self.param, self.native, 1.2),
            (self.param, self.npv, NPV_DivisionExpression((6, self.npv))),
            (self.param, self.param, 1.0),
            (self.param, self.param_mut, NPV_DivisionExpression((6, self.param_mut))),
            # 8:
            (self.param, self.var, DivisionExpression((6, self.var))),
            (self.param, self.mon_native, DivisionExpression((6, self.mon_native))),
            (self.param, self.mon_param, DivisionExpression((6, self.mon_param))),
            (self.param, self.mon_npv, DivisionExpression((6, self.mon_npv))),
            # 12:
            (self.param, self.linear, DivisionExpression((6, self.linear))),
            (self.param, self.sum, DivisionExpression((6, self.sum))),
            (self.param, self.other, DivisionExpression((6, self.other))),
            (self.param, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.param, self.mutable_l1, DivisionExpression((6, self.mon_npv))),
            (self.param, self.mutable_l2, DivisionExpression((6, self.l2))),
            (self.param, self.param0, ZeroDivisionError),
            (self.param, self.param1, 6.0),
            # 20:
            (self.param, self.mutable_l3, NPV_DivisionExpression((6, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_param_mut(self):
        tests = [
            (self.param_mut, self.invalid, NotImplemented),
            (
                self.param_mut,
                self.asbinary,
                DivisionExpression((self.param_mut, self.bin)),
            ),
            (self.param_mut, self.zero, ZeroDivisionError),
            (self.param_mut, self.one, self.param_mut),
            # 4:
            (self.param_mut, self.native, NPV_DivisionExpression((self.param_mut, 5))),
            (
                self.param_mut,
                self.npv,
                NPV_DivisionExpression((self.param_mut, self.npv)),
            ),
            (self.param_mut, self.param, NPV_DivisionExpression((self.param_mut, 6))),
            (
                self.param_mut,
                self.param_mut,
                NPV_DivisionExpression((self.param_mut, self.param_mut)),
            ),
            # 8:
            (self.param_mut, self.var, DivisionExpression((self.param_mut, self.var))),
            (
                self.param_mut,
                self.mon_native,
                DivisionExpression((self.param_mut, self.mon_native)),
            ),
            (
                self.param_mut,
                self.mon_param,
                DivisionExpression((self.param_mut, self.mon_param)),
            ),
            (
                self.param_mut,
                self.mon_npv,
                DivisionExpression((self.param_mut, self.mon_npv)),
            ),
            # 12:
            (
                self.param_mut,
                self.linear,
                DivisionExpression((self.param_mut, self.linear)),
            ),
            (self.param_mut, self.sum, DivisionExpression((self.param_mut, self.sum))),
            (
                self.param_mut,
                self.other,
                DivisionExpression((self.param_mut, self.other)),
            ),
            (self.param_mut, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.param_mut,
                self.mutable_l1,
                DivisionExpression((self.param_mut, self.mon_npv)),
            ),
            (
                self.param_mut,
                self.mutable_l2,
                DivisionExpression((self.param_mut, self.l2)),
            ),
            (self.param_mut, self.param0, ZeroDivisionError),
            (self.param_mut, self.param1, self.param_mut),
            # 20:
            (
                self.param_mut,
                self.mutable_l3,
                NPV_DivisionExpression((self.param_mut, self.npv)),
            ),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_var(self):
        tests = [
            (self.var, self.invalid, NotImplemented),
            (self.var, self.asbinary, DivisionExpression((self.var, self.bin))),
            (self.var, self.zero, ZeroDivisionError),
            (self.var, self.one, self.var),
            # 4:
            (self.var, self.native, MonomialTermExpression((0.2, self.var))),
            (
                self.var,
                self.npv,
                MonomialTermExpression(
                    (NPV_DivisionExpression((1, self.npv)), self.var)
                ),
            ),
            (self.var, self.param, MonomialTermExpression((1 / 6.0, self.var))),
            (
                self.var,
                self.param_mut,
                MonomialTermExpression(
                    (NPV_DivisionExpression((1, self.param_mut)), self.var)
                ),
            ),
            # 8:
            (self.var, self.var, DivisionExpression((self.var, self.var))),
            (
                self.var,
                self.mon_native,
                DivisionExpression((self.var, self.mon_native)),
            ),
            (self.var, self.mon_param, DivisionExpression((self.var, self.mon_param))),
            (self.var, self.mon_npv, DivisionExpression((self.var, self.mon_npv))),
            # 12:
            (self.var, self.linear, DivisionExpression((self.var, self.linear))),
            (self.var, self.sum, DivisionExpression((self.var, self.sum))),
            (self.var, self.other, DivisionExpression((self.var, self.other))),
            (self.var, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.var, self.mutable_l1, DivisionExpression((self.var, self.mon_npv))),
            (self.var, self.mutable_l2, DivisionExpression((self.var, self.l2))),
            (self.var, self.param0, ZeroDivisionError),
            (self.var, self.param1, self.var),
            # 20:
            (
                self.var,
                self.mutable_l3,
                MonomialTermExpression(
                    (NPV_DivisionExpression((1, self.npv)), self.var)
                ),
            ),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_mon_native(self):
        tests = [
            (self.mon_native, self.invalid, NotImplemented),
            (
                self.mon_native,
                self.asbinary,
                DivisionExpression((self.mon_native, self.bin)),
            ),
            (self.mon_native, self.zero, ZeroDivisionError),
            (self.mon_native, self.one, self.mon_native),
            # 4:
            (
                self.mon_native,
                self.native,
                MonomialTermExpression((0.6, self.mon_native.arg(1))),
            ),
            (
                self.mon_native,
                self.npv,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_native.arg(0), self.npv)),
                        self.mon_native.arg(1),
                    )
                ),
            ),
            (
                self.mon_native,
                self.param,
                MonomialTermExpression((0.5, self.mon_native.arg(1))),
            ),
            (
                self.mon_native,
                self.param_mut,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression(
                            (self.mon_native.arg(0), self.param_mut)
                        ),
                        self.mon_native.arg(1),
                    )
                ),
            ),
            # 8:
            (
                self.mon_native,
                self.var,
                DivisionExpression((self.mon_native, self.var)),
            ),
            (
                self.mon_native,
                self.mon_native,
                DivisionExpression((self.mon_native, self.mon_native)),
            ),
            (
                self.mon_native,
                self.mon_param,
                DivisionExpression((self.mon_native, self.mon_param)),
            ),
            (
                self.mon_native,
                self.mon_npv,
                DivisionExpression((self.mon_native, self.mon_npv)),
            ),
            # 12:
            (
                self.mon_native,
                self.linear,
                DivisionExpression((self.mon_native, self.linear)),
            ),
            (
                self.mon_native,
                self.sum,
                DivisionExpression((self.mon_native, self.sum)),
            ),
            (
                self.mon_native,
                self.other,
                DivisionExpression((self.mon_native, self.other)),
            ),
            (self.mon_native, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.mon_native,
                self.mutable_l1,
                DivisionExpression((self.mon_native, self.mon_npv)),
            ),
            (
                self.mon_native,
                self.mutable_l2,
                DivisionExpression((self.mon_native, self.l2)),
            ),
            (self.mon_native, self.param0, ZeroDivisionError),
            (self.mon_native, self.param1, self.mon_native),
            # 20:
            (
                self.mon_native,
                self.mutable_l3,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_native.arg(0), self.npv)),
                        self.mon_native.arg(1),
                    )
                ),
            ),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_mon_param(self):
        tests = [
            (self.mon_param, self.invalid, NotImplemented),
            (
                self.mon_param,
                self.asbinary,
                DivisionExpression((self.mon_param, self.bin)),
            ),
            (self.mon_param, self.zero, ZeroDivisionError),
            (self.mon_param, self.one, self.mon_param),
            # 4:
            (
                self.mon_param,
                self.native,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_param.arg(0), 5)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.mon_param,
                self.npv,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_param.arg(0), self.npv)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.mon_param,
                self.param,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_param.arg(0), 6)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            (
                self.mon_param,
                self.param_mut,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_param.arg(0), self.param_mut)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
            # 8:
            (self.mon_param, self.var, DivisionExpression((self.mon_param, self.var))),
            (
                self.mon_param,
                self.mon_native,
                DivisionExpression((self.mon_param, self.mon_native)),
            ),
            (
                self.mon_param,
                self.mon_param,
                DivisionExpression((self.mon_param, self.mon_param)),
            ),
            (
                self.mon_param,
                self.mon_npv,
                DivisionExpression((self.mon_param, self.mon_npv)),
            ),
            # 12:
            (
                self.mon_param,
                self.linear,
                DivisionExpression((self.mon_param, self.linear)),
            ),
            (self.mon_param, self.sum, DivisionExpression((self.mon_param, self.sum))),
            (
                self.mon_param,
                self.other,
                DivisionExpression((self.mon_param, self.other)),
            ),
            (self.mon_param, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.mon_param,
                self.mutable_l1,
                DivisionExpression((self.mon_param, self.mon_npv)),
            ),
            (
                self.mon_param,
                self.mutable_l2,
                DivisionExpression((self.mon_param, self.l2)),
            ),
            (self.mon_param, self.param0, ZeroDivisionError),
            (self.mon_param, self.param1, self.mon_param),
            # 20:
            (
                self.mon_param,
                self.mutable_l3,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_param.arg(0), self.npv)),
                        self.mon_param.arg(1),
                    )
                ),
            ),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_mon_npv(self):
        tests = [
            (self.mon_npv, self.invalid, NotImplemented),
            (self.mon_npv, self.asbinary, DivisionExpression((self.mon_npv, self.bin))),
            (self.mon_npv, self.zero, ZeroDivisionError),
            (self.mon_npv, self.one, self.mon_npv),
            # 4:
            (
                self.mon_npv,
                self.native,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), 5)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mon_npv,
                self.npv,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), self.npv)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mon_npv,
                self.param,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), 6)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mon_npv,
                self.param_mut,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), self.param_mut)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 8:
            (self.mon_npv, self.var, DivisionExpression((self.mon_npv, self.var))),
            (
                self.mon_npv,
                self.mon_native,
                DivisionExpression((self.mon_npv, self.mon_native)),
            ),
            (
                self.mon_npv,
                self.mon_param,
                DivisionExpression((self.mon_npv, self.mon_param)),
            ),
            (
                self.mon_npv,
                self.mon_npv,
                DivisionExpression((self.mon_npv, self.mon_npv)),
            ),
            # 12:
            (
                self.mon_npv,
                self.linear,
                DivisionExpression((self.mon_npv, self.linear)),
            ),
            (self.mon_npv, self.sum, DivisionExpression((self.mon_npv, self.sum))),
            (self.mon_npv, self.other, DivisionExpression((self.mon_npv, self.other))),
            (self.mon_npv, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.mon_npv,
                self.mutable_l1,
                DivisionExpression((self.mon_npv, self.mon_npv)),
            ),
            (
                self.mon_npv,
                self.mutable_l2,
                DivisionExpression((self.mon_npv, self.l2)),
            ),
            (self.mon_npv, self.param0, ZeroDivisionError),
            (self.mon_npv, self.param1, self.mon_npv),
            # 20:
            (
                self.mon_npv,
                self.mutable_l3,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), self.npv)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_linear(self):
        tests = [
            (self.linear, self.invalid, NotImplemented),
            (self.linear, self.asbinary, DivisionExpression((self.linear, self.bin))),
            (self.linear, self.zero, ZeroDivisionError),
            (self.linear, self.one, self.linear),
            # 4:
            (self.linear, self.native, DivisionExpression((self.linear, 5))),
            (self.linear, self.npv, DivisionExpression((self.linear, self.npv))),
            (self.linear, self.param, DivisionExpression((self.linear, 6))),
            (
                self.linear,
                self.param_mut,
                DivisionExpression((self.linear, self.param_mut)),
            ),
            # 8:
            (self.linear, self.var, DivisionExpression((self.linear, self.var))),
            (
                self.linear,
                self.mon_native,
                DivisionExpression((self.linear, self.mon_native)),
            ),
            (
                self.linear,
                self.mon_param,
                DivisionExpression((self.linear, self.mon_param)),
            ),
            (
                self.linear,
                self.mon_npv,
                DivisionExpression((self.linear, self.mon_npv)),
            ),
            # 12:
            (self.linear, self.linear, DivisionExpression((self.linear, self.linear))),
            (self.linear, self.sum, DivisionExpression((self.linear, self.sum))),
            (self.linear, self.other, DivisionExpression((self.linear, self.other))),
            (self.linear, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.linear,
                self.mutable_l1,
                DivisionExpression((self.linear, self.mon_npv)),
            ),
            (self.linear, self.mutable_l2, DivisionExpression((self.linear, self.l2))),
            (self.linear, self.param0, ZeroDivisionError),
            (self.linear, self.param1, self.linear),
            # 20:
            (self.linear, self.mutable_l3, DivisionExpression((self.linear, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_sum(self):
        tests = [
            (self.sum, self.invalid, NotImplemented),
            (self.sum, self.asbinary, DivisionExpression((self.sum, self.bin))),
            (self.sum, self.zero, ZeroDivisionError),
            (self.sum, self.one, self.sum),
            # 4:
            (self.sum, self.native, DivisionExpression((self.sum, 5))),
            (self.sum, self.npv, DivisionExpression((self.sum, self.npv))),
            (self.sum, self.param, DivisionExpression((self.sum, 6))),
            (self.sum, self.param_mut, DivisionExpression((self.sum, self.param_mut))),
            # 8:
            (self.sum, self.var, DivisionExpression((self.sum, self.var))),
            (
                self.sum,
                self.mon_native,
                DivisionExpression((self.sum, self.mon_native)),
            ),
            (self.sum, self.mon_param, DivisionExpression((self.sum, self.mon_param))),
            (self.sum, self.mon_npv, DivisionExpression((self.sum, self.mon_npv))),
            # 12:
            (self.sum, self.linear, DivisionExpression((self.sum, self.linear))),
            (self.sum, self.sum, DivisionExpression((self.sum, self.sum))),
            (self.sum, self.other, DivisionExpression((self.sum, self.other))),
            (self.sum, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.sum, self.mutable_l1, DivisionExpression((self.sum, self.mon_npv))),
            (self.sum, self.mutable_l2, DivisionExpression((self.sum, self.l2))),
            (self.sum, self.param0, ZeroDivisionError),
            (self.sum, self.param1, self.sum),
            # 20:
            (self.sum, self.mutable_l3, DivisionExpression((self.sum, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_other(self):
        tests = [
            (self.other, self.invalid, NotImplemented),
            (self.other, self.asbinary, DivisionExpression((self.other, self.bin))),
            (self.other, self.zero, ZeroDivisionError),
            (self.other, self.one, self.other),
            # 4:
            (self.other, self.native, DivisionExpression((self.other, 5))),
            (self.other, self.npv, DivisionExpression((self.other, self.npv))),
            (self.other, self.param, DivisionExpression((self.other, 6))),
            (
                self.other,
                self.param_mut,
                DivisionExpression((self.other, self.param_mut)),
            ),
            # 8:
            (self.other, self.var, DivisionExpression((self.other, self.var))),
            (
                self.other,
                self.mon_native,
                DivisionExpression((self.other, self.mon_native)),
            ),
            (
                self.other,
                self.mon_param,
                DivisionExpression((self.other, self.mon_param)),
            ),
            (self.other, self.mon_npv, DivisionExpression((self.other, self.mon_npv))),
            # 12:
            (self.other, self.linear, DivisionExpression((self.other, self.linear))),
            (self.other, self.sum, DivisionExpression((self.other, self.sum))),
            (self.other, self.other, DivisionExpression((self.other, self.other))),
            (self.other, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.other,
                self.mutable_l1,
                DivisionExpression((self.other, self.mon_npv)),
            ),
            (self.other, self.mutable_l2, DivisionExpression((self.other, self.l2))),
            (self.other, self.param0, ZeroDivisionError),
            (self.other, self.param1, self.other),
            # 20:
            (self.other, self.mutable_l3, DivisionExpression((self.other, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_mutable_l0(self):
        tests = [
            (self.mutable_l0, self.invalid, NotImplemented),
            (self.mutable_l0, self.asbinary, DivisionExpression((0, self.bin))),
            (self.mutable_l0, self.zero, ZeroDivisionError),
            (self.mutable_l0, self.one, 0.0),
            # 4:
            (self.mutable_l0, self.native, 0.0),
            (self.mutable_l0, self.npv, NPV_DivisionExpression((0, self.npv))),
            (self.mutable_l0, self.param, 0.0),
            (
                self.mutable_l0,
                self.param_mut,
                NPV_DivisionExpression((0, self.param_mut)),
            ),
            # 8:
            (self.mutable_l0, self.var, DivisionExpression((0, self.var))),
            (
                self.mutable_l0,
                self.mon_native,
                DivisionExpression((0, self.mon_native)),
            ),
            (self.mutable_l0, self.mon_param, DivisionExpression((0, self.mon_param))),
            (self.mutable_l0, self.mon_npv, DivisionExpression((0, self.mon_npv))),
            # 12:
            (self.mutable_l0, self.linear, DivisionExpression((0, self.linear))),
            (self.mutable_l0, self.sum, DivisionExpression((0, self.sum))),
            (self.mutable_l0, self.other, DivisionExpression((0, self.other))),
            (self.mutable_l0, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.mutable_l0, self.mutable_l1, DivisionExpression((0, self.mon_npv))),
            (self.mutable_l0, self.mutable_l2, DivisionExpression((0, self.l2))),
            (self.mutable_l0, self.param0, ZeroDivisionError),
            (self.mutable_l0, self.param1, 0.0),
            # 20:
            (self.mutable_l0, self.mutable_l3, NPV_DivisionExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_mutable_l1(self):
        tests = [
            (self.mutable_l1, self.invalid, NotImplemented),
            (
                self.mutable_l1,
                self.asbinary,
                DivisionExpression((self.mon_npv, self.bin)),
            ),
            (self.mutable_l1, self.zero, ZeroDivisionError),
            (self.mutable_l1, self.one, self.mon_npv),
            # 4:
            (
                self.mutable_l1,
                self.native,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), self.native)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l1,
                self.npv,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), self.npv)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l1,
                self.param,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), 6)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            (
                self.mutable_l1,
                self.param_mut,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), self.param_mut)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
            # 8:
            (self.mutable_l1, self.var, DivisionExpression((self.mon_npv, self.var))),
            (
                self.mutable_l1,
                self.mon_native,
                DivisionExpression((self.mon_npv, self.mon_native)),
            ),
            (
                self.mutable_l1,
                self.mon_param,
                DivisionExpression((self.mon_npv, self.mon_param)),
            ),
            (
                self.mutable_l1,
                self.mon_npv,
                DivisionExpression((self.mon_npv, self.mon_npv)),
            ),
            # 12:
            (
                self.mutable_l1,
                self.linear,
                DivisionExpression((self.mon_npv, self.linear)),
            ),
            (self.mutable_l1, self.sum, DivisionExpression((self.mon_npv, self.sum))),
            (
                self.mutable_l1,
                self.other,
                DivisionExpression((self.mon_npv, self.other)),
            ),
            (self.mutable_l1, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.mutable_l1,
                self.mutable_l1,
                DivisionExpression((self.mon_npv, self.mon_npv)),
            ),
            (
                self.mutable_l1,
                self.mutable_l2,
                DivisionExpression((self.mon_npv, self.l2)),
            ),
            (self.mutable_l1, self.param0, ZeroDivisionError),
            (self.mutable_l1, self.param1, self.mon_npv),
            # 20:
            (
                self.mutable_l1,
                self.mutable_l3,
                MonomialTermExpression(
                    (
                        NPV_DivisionExpression((self.mon_npv.arg(0), self.npv)),
                        self.mon_npv.arg(1),
                    )
                ),
            ),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_mutable_l2(self):
        tests = [
            (self.mutable_l2, self.invalid, NotImplemented),
            (self.mutable_l2, self.asbinary, DivisionExpression((self.l2, self.bin))),
            (self.mutable_l2, self.zero, ZeroDivisionError),
            (self.mutable_l2, self.one, self.l2),
            # 4:
            (self.mutable_l2, self.native, DivisionExpression((self.l2, 5))),
            (self.mutable_l2, self.npv, DivisionExpression((self.l2, self.npv))),
            (self.mutable_l2, self.param, DivisionExpression((self.l2, 6))),
            (
                self.mutable_l2,
                self.param_mut,
                DivisionExpression((self.l2, self.param_mut)),
            ),
            # 8:
            (self.mutable_l2, self.var, DivisionExpression((self.l2, self.var))),
            (
                self.mutable_l2,
                self.mon_native,
                DivisionExpression((self.l2, self.mon_native)),
            ),
            (
                self.mutable_l2,
                self.mon_param,
                DivisionExpression((self.l2, self.mon_param)),
            ),
            (
                self.mutable_l2,
                self.mon_npv,
                DivisionExpression((self.l2, self.mon_npv)),
            ),
            # 12:
            (self.mutable_l2, self.linear, DivisionExpression((self.l2, self.linear))),
            (self.mutable_l2, self.sum, DivisionExpression((self.l2, self.sum))),
            (self.mutable_l2, self.other, DivisionExpression((self.l2, self.other))),
            (self.mutable_l2, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.mutable_l2,
                self.mutable_l1,
                DivisionExpression((self.l2, self.mon_npv)),
            ),
            (self.mutable_l2, self.mutable_l2, DivisionExpression((self.l2, self.l2))),
            (self.mutable_l2, self.param0, ZeroDivisionError),
            (self.mutable_l2, self.param1, self.l2),
            # 20:
            (self.mutable_l2, self.mutable_l3, DivisionExpression((self.l2, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_param0(self):
        tests = [
            (self.param0, self.invalid, NotImplemented),
            (self.param0, self.asbinary, DivisionExpression((0, self.bin))),
            (self.param0, self.zero, ZeroDivisionError),
            (self.param0, self.one, 0.0),
            # 4:
            (self.param0, self.native, 0.0),
            (self.param0, self.npv, NPV_DivisionExpression((0, self.npv))),
            (self.param0, self.param, 0.0),
            (self.param0, self.param_mut, NPV_DivisionExpression((0, self.param_mut))),
            # 8:
            (self.param0, self.var, DivisionExpression((0, self.var))),
            (self.param0, self.mon_native, DivisionExpression((0, self.mon_native))),
            (self.param0, self.mon_param, DivisionExpression((0, self.mon_param))),
            (self.param0, self.mon_npv, DivisionExpression((0, self.mon_npv))),
            # 12:
            (self.param0, self.linear, DivisionExpression((0, self.linear))),
            (self.param0, self.sum, DivisionExpression((0, self.sum))),
            (self.param0, self.other, DivisionExpression((0, self.other))),
            (self.param0, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.param0, self.mutable_l1, DivisionExpression((0, self.mon_npv))),
            (self.param0, self.mutable_l2, DivisionExpression((0, self.l2))),
            (self.param0, self.param0, ZeroDivisionError),
            (self.param0, self.param1, 0.0),
            # 20:
            (self.param0, self.mutable_l3, NPV_DivisionExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_param1(self):
        tests = [
            (self.param1, self.invalid, NotImplemented),
            (self.param1, self.asbinary, DivisionExpression((1, self.bin))),
            (self.param1, self.zero, ZeroDivisionError),
            (self.param1, self.one, 1.0),
            # 4:
            (self.param1, self.native, 0.2),
            (self.param1, self.npv, NPV_DivisionExpression((1, self.npv))),
            (self.param1, self.param, 1 / 6),
            (self.param1, self.param_mut, NPV_DivisionExpression((1, self.param_mut))),
            # 8:
            (self.param1, self.var, DivisionExpression((1, self.var))),
            (self.param1, self.mon_native, DivisionExpression((1, self.mon_native))),
            (self.param1, self.mon_param, DivisionExpression((1, self.mon_param))),
            (self.param1, self.mon_npv, DivisionExpression((1, self.mon_npv))),
            # 12:
            (self.param1, self.linear, DivisionExpression((1, self.linear))),
            (self.param1, self.sum, DivisionExpression((1, self.sum))),
            (self.param1, self.other, DivisionExpression((1, self.other))),
            (self.param1, self.mutable_l0, ZeroDivisionError),
            # 16:
            (self.param1, self.mutable_l1, DivisionExpression((1, self.mon_npv))),
            (self.param1, self.mutable_l2, DivisionExpression((1, self.l2))),
            (self.param1, self.param0, ZeroDivisionError),
            (self.param1, self.param1, 1.0),
            # 20:
            (self.param1, self.mutable_l3, NPV_DivisionExpression((1, self.npv))),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    def test_div_mutable_l3(self):
        tests = [
            (self.mutable_l3, self.invalid, NotImplemented),
            (self.mutable_l3, self.asbinary, DivisionExpression((self.npv, self.bin))),
            (self.mutable_l3, self.zero, ZeroDivisionError),
            (self.mutable_l3, self.one, self.npv),
            # 4:
            (self.mutable_l3, self.native, NPV_DivisionExpression((self.npv, 5))),
            (self.mutable_l3, self.npv, NPV_DivisionExpression((self.npv, self.npv))),
            (self.mutable_l3, self.param, NPV_DivisionExpression((self.npv, 6))),
            (
                self.mutable_l3,
                self.param_mut,
                NPV_DivisionExpression((self.npv, self.param_mut)),
            ),
            # 8:
            (self.mutable_l3, self.var, DivisionExpression((self.npv, self.var))),
            (
                self.mutable_l3,
                self.mon_native,
                DivisionExpression((self.npv, self.mon_native)),
            ),
            (
                self.mutable_l3,
                self.mon_param,
                DivisionExpression((self.npv, self.mon_param)),
            ),
            (
                self.mutable_l3,
                self.mon_npv,
                DivisionExpression((self.npv, self.mon_npv)),
            ),
            # 12:
            (self.mutable_l3, self.linear, DivisionExpression((self.npv, self.linear))),
            (self.mutable_l3, self.sum, DivisionExpression((self.npv, self.sum))),
            (self.mutable_l3, self.other, DivisionExpression((self.npv, self.other))),
            (self.mutable_l3, self.mutable_l0, ZeroDivisionError),
            # 16:
            (
                self.mutable_l3,
                self.mutable_l1,
                DivisionExpression((self.npv, self.mon_npv)),
            ),
            (self.mutable_l3, self.mutable_l2, DivisionExpression((self.npv, self.l2))),
            (self.mutable_l3, self.param0, ZeroDivisionError),
            (self.mutable_l3, self.param1, self.npv),
            # 20:
            (
                self.mutable_l3,
                self.mutable_l3,
                NPV_DivisionExpression((self.npv, self.npv)),
            ),
        ]
        self._run_cases(tests, operator.truediv)
        self._run_cases(tests, operator.itruediv)

    #
    #
    # EXPONENTIATION
    #
    #

    def test_pow_invalid(self):
        tests = [
            (self.invalid, self.invalid, NotImplemented),
            (self.invalid, self.asbinary, NotImplemented),
            (self.invalid, self.zero, NotImplemented),
            (self.invalid, self.one, NotImplemented),
            # 4:
            (self.invalid, self.native, NotImplemented),
            (self.invalid, self.npv, NotImplemented),
            (self.invalid, self.param, NotImplemented),
            (self.invalid, self.param_mut, NotImplemented),
            # 8:
            (self.invalid, self.var, NotImplemented),
            (self.invalid, self.mon_native, NotImplemented),
            (self.invalid, self.mon_param, NotImplemented),
            (self.invalid, self.mon_npv, NotImplemented),
            # 12:
            (self.invalid, self.linear, NotImplemented),
            (self.invalid, self.sum, NotImplemented),
            (self.invalid, self.other, NotImplemented),
            (self.invalid, self.mutable_l0, NotImplemented),
            # 16:
            (self.invalid, self.mutable_l1, NotImplemented),
            (self.invalid, self.mutable_l2, NotImplemented),
            (self.invalid, self.param0, NotImplemented),
            (self.invalid, self.param1, NotImplemented),
            # 20:
            (self.invalid, self.mutable_l3, NotImplemented),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_asbinary(self):
        tests = [
            (self.asbinary, self.invalid, NotImplemented),
            # BooleanVar objects do not support exponentiation
            (self.asbinary, self.asbinary, NotImplemented),
            (self.asbinary, self.zero, PowExpression((self.bin, 0))),
            (self.asbinary, self.one, self.bin),
            # 4:
            (self.asbinary, self.native, PowExpression((self.bin, 5))),
            (self.asbinary, self.npv, PowExpression((self.bin, self.npv))),
            (self.asbinary, self.param, PowExpression((self.bin, 6))),
            (self.asbinary, self.param_mut, PowExpression((self.bin, self.param_mut))),
            # 8:
            (self.asbinary, self.var, PowExpression((self.bin, self.var))),
            (
                self.asbinary,
                self.mon_native,
                PowExpression((self.bin, self.mon_native)),
            ),
            (self.asbinary, self.mon_param, PowExpression((self.bin, self.mon_param))),
            (self.asbinary, self.mon_npv, PowExpression((self.bin, self.mon_npv))),
            # 12:
            (self.asbinary, self.linear, PowExpression((self.bin, self.linear))),
            (self.asbinary, self.sum, PowExpression((self.bin, self.sum))),
            (self.asbinary, self.other, PowExpression((self.bin, self.other))),
            (self.asbinary, self.mutable_l0, PowExpression((self.bin, 0))),
            # 16:
            (self.asbinary, self.mutable_l1, PowExpression((self.bin, self.mon_npv))),
            (self.asbinary, self.mutable_l2, PowExpression((self.bin, self.l2))),
            (self.asbinary, self.param0, PowExpression((self.bin, 0))),
            (self.asbinary, self.param1, self.bin),
            # 20:
            (self.asbinary, self.mutable_l3, PowExpression((self.bin, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_zero(self):
        tests = [
            (self.zero, self.invalid, NotImplemented),
            (self.zero, self.asbinary, PowExpression((0, self.bin))),
            (self.zero, self.zero, 1),
            (self.zero, self.one, 0),
            # 4:
            (self.zero, self.native, 0),
            (self.zero, self.npv, NPV_PowExpression((0, self.npv))),
            (self.zero, self.param, 0),
            (self.zero, self.param_mut, NPV_PowExpression((0, self.param_mut))),
            # 8:
            (self.zero, self.var, PowExpression((0, self.var))),
            (self.zero, self.mon_native, PowExpression((0, self.mon_native))),
            (self.zero, self.mon_param, PowExpression((0, self.mon_param))),
            (self.zero, self.mon_npv, PowExpression((0, self.mon_npv))),
            # 12:
            (self.zero, self.linear, PowExpression((0, self.linear))),
            (self.zero, self.sum, PowExpression((0, self.sum))),
            (self.zero, self.other, PowExpression((0, self.other))),
            (self.zero, self.mutable_l0, 1),
            # 16:
            (self.zero, self.mutable_l1, PowExpression((0, self.mon_npv))),
            (self.zero, self.mutable_l2, PowExpression((0, self.l2))),
            (self.zero, self.param0, 1),
            (self.zero, self.param1, 0),
            # 20:
            (self.zero, self.mutable_l3, NPV_PowExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_one(self):
        tests = [
            (self.one, self.invalid, NotImplemented),
            (self.one, self.asbinary, PowExpression((1, self.bin))),
            (self.one, self.zero, 1),
            (self.one, self.one, 1),
            # 4:
            (self.one, self.native, 1),
            (self.one, self.npv, NPV_PowExpression((1, self.npv))),
            (self.one, self.param, 1),
            (self.one, self.param_mut, NPV_PowExpression((1, self.param_mut))),
            # 8:
            (self.one, self.var, PowExpression((1, self.var))),
            (self.one, self.mon_native, PowExpression((1, self.mon_native))),
            (self.one, self.mon_param, PowExpression((1, self.mon_param))),
            (self.one, self.mon_npv, PowExpression((1, self.mon_npv))),
            # 12:
            (self.one, self.linear, PowExpression((1, self.linear))),
            (self.one, self.sum, PowExpression((1, self.sum))),
            (self.one, self.other, PowExpression((1, self.other))),
            (self.one, self.mutable_l0, 1),
            # 16:
            (self.one, self.mutable_l1, PowExpression((1, self.mon_npv))),
            (self.one, self.mutable_l2, PowExpression((1, self.l2))),
            (self.one, self.param0, 1),
            (self.one, self.param1, 1),
            # 20:
            (self.one, self.mutable_l3, NPV_PowExpression((1, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_native(self):
        tests = [
            (self.native, self.invalid, NotImplemented),
            (self.native, self.asbinary, PowExpression((5, self.bin))),
            (self.native, self.zero, 1),
            (self.native, self.one, 5),
            # 4:
            (self.native, self.native, 3125),
            (self.native, self.npv, NPV_PowExpression((5, self.npv))),
            (self.native, self.param, 15625),
            (self.native, self.param_mut, NPV_PowExpression((5, self.param_mut))),
            # 8:
            (self.native, self.var, PowExpression((5, self.var))),
            (self.native, self.mon_native, PowExpression((5, self.mon_native))),
            (self.native, self.mon_param, PowExpression((5, self.mon_param))),
            (self.native, self.mon_npv, PowExpression((5, self.mon_npv))),
            # 12:
            (self.native, self.linear, PowExpression((5, self.linear))),
            (self.native, self.sum, PowExpression((5, self.sum))),
            (self.native, self.other, PowExpression((5, self.other))),
            (self.native, self.mutable_l0, 1),
            # 16:
            (self.native, self.mutable_l1, PowExpression((5, self.mon_npv))),
            (self.native, self.mutable_l2, PowExpression((5, self.l2))),
            (self.native, self.param0, 1),
            (self.native, self.param1, 5),
            # 20:
            (self.native, self.mutable_l3, NPV_PowExpression((5, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_npv(self):
        tests = [
            (self.npv, self.invalid, NotImplemented),
            (self.npv, self.asbinary, PowExpression((self.npv, self.bin))),
            (self.npv, self.zero, NPV_PowExpression((self.npv, 0))),
            (self.npv, self.one, self.npv),
            # 4:
            (self.npv, self.native, NPV_PowExpression((self.npv, 5))),
            (self.npv, self.npv, NPV_PowExpression((self.npv, self.npv))),
            (self.npv, self.param, NPV_PowExpression((self.npv, 6))),
            (self.npv, self.param_mut, NPV_PowExpression((self.npv, self.param_mut))),
            # 8:
            (self.npv, self.var, PowExpression((self.npv, self.var))),
            (self.npv, self.mon_native, PowExpression((self.npv, self.mon_native))),
            (self.npv, self.mon_param, PowExpression((self.npv, self.mon_param))),
            (self.npv, self.mon_npv, PowExpression((self.npv, self.mon_npv))),
            # 12:
            (self.npv, self.linear, PowExpression((self.npv, self.linear))),
            (self.npv, self.sum, PowExpression((self.npv, self.sum))),
            (self.npv, self.other, PowExpression((self.npv, self.other))),
            (self.npv, self.mutable_l0, NPV_PowExpression((self.npv, 0))),
            # 16:
            (self.npv, self.mutable_l1, PowExpression((self.npv, self.mon_npv))),
            (self.npv, self.mutable_l2, PowExpression((self.npv, self.l2))),
            (self.npv, self.param0, NPV_PowExpression((self.npv, 0))),
            (self.npv, self.param1, self.npv),
            # 20:
            (self.npv, self.mutable_l3, NPV_PowExpression((self.npv, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_param(self):
        tests = [
            (self.param, self.invalid, NotImplemented),
            (self.param, self.asbinary, PowExpression((6, self.bin))),
            (self.param, self.zero, 1),
            (self.param, self.one, 6),
            # 4:
            (self.param, self.native, 7776),
            (self.param, self.npv, NPV_PowExpression((6, self.npv))),
            (self.param, self.param, 46656),
            (self.param, self.param_mut, NPV_PowExpression((6, self.param_mut))),
            # 8:
            (self.param, self.var, PowExpression((6, self.var))),
            (self.param, self.mon_native, PowExpression((6, self.mon_native))),
            (self.param, self.mon_param, PowExpression((6, self.mon_param))),
            (self.param, self.mon_npv, PowExpression((6, self.mon_npv))),
            # 12:
            (self.param, self.linear, PowExpression((6, self.linear))),
            (self.param, self.sum, PowExpression((6, self.sum))),
            (self.param, self.other, PowExpression((6, self.other))),
            (self.param, self.mutable_l0, 1),
            # 16:
            (self.param, self.mutable_l1, PowExpression((6, self.mon_npv))),
            (self.param, self.mutable_l2, PowExpression((6, self.l2))),
            (self.param, self.param0, 1),
            (self.param, self.param1, 6),
            # 20:
            (self.param, self.mutable_l3, NPV_PowExpression((6, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_param_mut(self):
        tests = [
            (self.param_mut, self.invalid, NotImplemented),
            (self.param_mut, self.asbinary, PowExpression((self.param_mut, self.bin))),
            (self.param_mut, self.zero, NPV_PowExpression((self.param_mut, 0))),
            (self.param_mut, self.one, self.param_mut),
            # 4:
            (self.param_mut, self.native, NPV_PowExpression((self.param_mut, 5))),
            (self.param_mut, self.npv, NPV_PowExpression((self.param_mut, self.npv))),
            (self.param_mut, self.param, NPV_PowExpression((self.param_mut, 6))),
            (
                self.param_mut,
                self.param_mut,
                NPV_PowExpression((self.param_mut, self.param_mut)),
            ),
            # 8:
            (self.param_mut, self.var, PowExpression((self.param_mut, self.var))),
            (
                self.param_mut,
                self.mon_native,
                PowExpression((self.param_mut, self.mon_native)),
            ),
            (
                self.param_mut,
                self.mon_param,
                PowExpression((self.param_mut, self.mon_param)),
            ),
            (
                self.param_mut,
                self.mon_npv,
                PowExpression((self.param_mut, self.mon_npv)),
            ),
            # 12:
            (self.param_mut, self.linear, PowExpression((self.param_mut, self.linear))),
            (self.param_mut, self.sum, PowExpression((self.param_mut, self.sum))),
            (self.param_mut, self.other, PowExpression((self.param_mut, self.other))),
            (self.param_mut, self.mutable_l0, NPV_PowExpression((self.param_mut, 0))),
            # 16:
            (
                self.param_mut,
                self.mutable_l1,
                PowExpression((self.param_mut, self.mon_npv)),
            ),
            (self.param_mut, self.mutable_l2, PowExpression((self.param_mut, self.l2))),
            (self.param_mut, self.param0, NPV_PowExpression((self.param_mut, 0))),
            (self.param_mut, self.param1, self.param_mut),
            # 20:
            (
                self.param_mut,
                self.mutable_l3,
                NPV_PowExpression((self.param_mut, self.npv)),
            ),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_var(self):
        tests = [
            (self.var, self.invalid, NotImplemented),
            (self.var, self.asbinary, PowExpression((self.var, self.bin))),
            (self.var, self.zero, PowExpression((self.var, 0))),
            (self.var, self.one, self.var),
            # 4:
            (self.var, self.native, PowExpression((self.var, 5))),
            (self.var, self.npv, PowExpression((self.var, self.npv))),
            (self.var, self.param, PowExpression((self.var, 6))),
            (self.var, self.param_mut, PowExpression((self.var, self.param_mut))),
            # 8:
            (self.var, self.var, PowExpression((self.var, self.var))),
            (self.var, self.mon_native, PowExpression((self.var, self.mon_native))),
            (self.var, self.mon_param, PowExpression((self.var, self.mon_param))),
            (self.var, self.mon_npv, PowExpression((self.var, self.mon_npv))),
            # 12:
            (self.var, self.linear, PowExpression((self.var, self.linear))),
            (self.var, self.sum, PowExpression((self.var, self.sum))),
            (self.var, self.other, PowExpression((self.var, self.other))),
            (self.var, self.mutable_l0, PowExpression((self.var, 0))),
            # 16:
            (self.var, self.mutable_l1, PowExpression((self.var, self.mon_npv))),
            (self.var, self.mutable_l2, PowExpression((self.var, self.l2))),
            (self.var, self.param0, PowExpression((self.var, 0))),
            (self.var, self.param1, self.var),
            # 20:
            (self.var, self.mutable_l3, PowExpression((self.var, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_mon_native(self):
        tests = [
            (self.mon_native, self.invalid, NotImplemented),
            (
                self.mon_native,
                self.asbinary,
                PowExpression((self.mon_native, self.bin)),
            ),
            (self.mon_native, self.zero, PowExpression((self.mon_native, 0))),
            (self.mon_native, self.one, self.mon_native),
            # 4:
            (self.mon_native, self.native, PowExpression((self.mon_native, 5))),
            (self.mon_native, self.npv, PowExpression((self.mon_native, self.npv))),
            (self.mon_native, self.param, PowExpression((self.mon_native, 6))),
            (
                self.mon_native,
                self.param_mut,
                PowExpression((self.mon_native, self.param_mut)),
            ),
            # 8:
            (self.mon_native, self.var, PowExpression((self.mon_native, self.var))),
            (
                self.mon_native,
                self.mon_native,
                PowExpression((self.mon_native, self.mon_native)),
            ),
            (
                self.mon_native,
                self.mon_param,
                PowExpression((self.mon_native, self.mon_param)),
            ),
            (
                self.mon_native,
                self.mon_npv,
                PowExpression((self.mon_native, self.mon_npv)),
            ),
            # 12:
            (
                self.mon_native,
                self.linear,
                PowExpression((self.mon_native, self.linear)),
            ),
            (self.mon_native, self.sum, PowExpression((self.mon_native, self.sum))),
            (self.mon_native, self.other, PowExpression((self.mon_native, self.other))),
            (self.mon_native, self.mutable_l0, PowExpression((self.mon_native, 0))),
            # 16:
            (
                self.mon_native,
                self.mutable_l1,
                PowExpression((self.mon_native, self.mon_npv)),
            ),
            (
                self.mon_native,
                self.mutable_l2,
                PowExpression((self.mon_native, self.l2)),
            ),
            (self.mon_native, self.param0, PowExpression((self.mon_native, 0))),
            (self.mon_native, self.param1, self.mon_native),
            # 20:
            (
                self.mon_native,
                self.mutable_l3,
                PowExpression((self.mon_native, self.npv)),
            ),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_mon_param(self):
        tests = [
            (self.mon_param, self.invalid, NotImplemented),
            (self.mon_param, self.asbinary, PowExpression((self.mon_param, self.bin))),
            (self.mon_param, self.zero, PowExpression((self.mon_param, 0))),
            (self.mon_param, self.one, self.mon_param),
            # 4:
            (self.mon_param, self.native, PowExpression((self.mon_param, 5))),
            (self.mon_param, self.npv, PowExpression((self.mon_param, self.npv))),
            (self.mon_param, self.param, PowExpression((self.mon_param, 6))),
            (
                self.mon_param,
                self.param_mut,
                PowExpression((self.mon_param, self.param_mut)),
            ),
            # 8:
            (self.mon_param, self.var, PowExpression((self.mon_param, self.var))),
            (
                self.mon_param,
                self.mon_native,
                PowExpression((self.mon_param, self.mon_native)),
            ),
            (
                self.mon_param,
                self.mon_param,
                PowExpression((self.mon_param, self.mon_param)),
            ),
            (
                self.mon_param,
                self.mon_npv,
                PowExpression((self.mon_param, self.mon_npv)),
            ),
            # 12:
            (self.mon_param, self.linear, PowExpression((self.mon_param, self.linear))),
            (self.mon_param, self.sum, PowExpression((self.mon_param, self.sum))),
            (self.mon_param, self.other, PowExpression((self.mon_param, self.other))),
            (self.mon_param, self.mutable_l0, PowExpression((self.mon_param, 0))),
            # 16:
            (
                self.mon_param,
                self.mutable_l1,
                PowExpression((self.mon_param, self.mon_npv)),
            ),
            (self.mon_param, self.mutable_l2, PowExpression((self.mon_param, self.l2))),
            (self.mon_param, self.param0, PowExpression((self.mon_param, 0))),
            (self.mon_param, self.param1, self.mon_param),
            # 20:
            (
                self.mon_param,
                self.mutable_l3,
                PowExpression((self.mon_param, self.npv)),
            ),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_mon_npv(self):
        tests = [
            (self.mon_npv, self.invalid, NotImplemented),
            (self.mon_npv, self.asbinary, PowExpression((self.mon_npv, self.bin))),
            (self.mon_npv, self.zero, PowExpression((self.mon_npv, 0))),
            (self.mon_npv, self.one, self.mon_npv),
            # 4:
            (self.mon_npv, self.native, PowExpression((self.mon_npv, 5))),
            (self.mon_npv, self.npv, PowExpression((self.mon_npv, self.npv))),
            (self.mon_npv, self.param, PowExpression((self.mon_npv, 6))),
            (
                self.mon_npv,
                self.param_mut,
                PowExpression((self.mon_npv, self.param_mut)),
            ),
            # 8:
            (self.mon_npv, self.var, PowExpression((self.mon_npv, self.var))),
            (
                self.mon_npv,
                self.mon_native,
                PowExpression((self.mon_npv, self.mon_native)),
            ),
            (
                self.mon_npv,
                self.mon_param,
                PowExpression((self.mon_npv, self.mon_param)),
            ),
            (self.mon_npv, self.mon_npv, PowExpression((self.mon_npv, self.mon_npv))),
            # 12:
            (self.mon_npv, self.linear, PowExpression((self.mon_npv, self.linear))),
            (self.mon_npv, self.sum, PowExpression((self.mon_npv, self.sum))),
            (self.mon_npv, self.other, PowExpression((self.mon_npv, self.other))),
            (self.mon_npv, self.mutable_l0, PowExpression((self.mon_npv, 0))),
            # 16:
            (
                self.mon_npv,
                self.mutable_l1,
                PowExpression((self.mon_npv, self.mon_npv)),
            ),
            (self.mon_npv, self.mutable_l2, PowExpression((self.mon_npv, self.l2))),
            (self.mon_npv, self.param0, PowExpression((self.mon_npv, 0))),
            (self.mon_npv, self.param1, self.mon_npv),
            # 20:
            (self.mon_npv, self.mutable_l3, PowExpression((self.mon_npv, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_linear(self):
        tests = [
            (self.linear, self.invalid, NotImplemented),
            (self.linear, self.asbinary, PowExpression((self.linear, self.bin))),
            (self.linear, self.zero, PowExpression((self.linear, 0))),
            (self.linear, self.one, self.linear),
            # 4:
            (self.linear, self.native, PowExpression((self.linear, 5))),
            (self.linear, self.npv, PowExpression((self.linear, self.npv))),
            (self.linear, self.param, PowExpression((self.linear, 6))),
            (self.linear, self.param_mut, PowExpression((self.linear, self.param_mut))),
            # 8:
            (self.linear, self.var, PowExpression((self.linear, self.var))),
            (
                self.linear,
                self.mon_native,
                PowExpression((self.linear, self.mon_native)),
            ),
            (self.linear, self.mon_param, PowExpression((self.linear, self.mon_param))),
            (self.linear, self.mon_npv, PowExpression((self.linear, self.mon_npv))),
            # 12:
            (self.linear, self.linear, PowExpression((self.linear, self.linear))),
            (self.linear, self.sum, PowExpression((self.linear, self.sum))),
            (self.linear, self.other, PowExpression((self.linear, self.other))),
            (self.linear, self.mutable_l0, PowExpression((self.linear, 0))),
            # 16:
            (self.linear, self.mutable_l1, PowExpression((self.linear, self.mon_npv))),
            (self.linear, self.mutable_l2, PowExpression((self.linear, self.l2))),
            (self.linear, self.param0, PowExpression((self.linear, 0))),
            (self.linear, self.param1, self.linear),
            # 20:
            (self.linear, self.mutable_l3, PowExpression((self.linear, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_sum(self):
        tests = [
            (self.sum, self.invalid, NotImplemented),
            (self.sum, self.asbinary, PowExpression((self.sum, self.bin))),
            (self.sum, self.zero, PowExpression((self.sum, 0))),
            (self.sum, self.one, self.sum),
            # 4:
            (self.sum, self.native, PowExpression((self.sum, 5))),
            (self.sum, self.npv, PowExpression((self.sum, self.npv))),
            (self.sum, self.param, PowExpression((self.sum, 6))),
            (self.sum, self.param_mut, PowExpression((self.sum, self.param_mut))),
            # 8:
            (self.sum, self.var, PowExpression((self.sum, self.var))),
            (self.sum, self.mon_native, PowExpression((self.sum, self.mon_native))),
            (self.sum, self.mon_param, PowExpression((self.sum, self.mon_param))),
            (self.sum, self.mon_npv, PowExpression((self.sum, self.mon_npv))),
            # 12:
            (self.sum, self.linear, PowExpression((self.sum, self.linear))),
            (self.sum, self.sum, PowExpression((self.sum, self.sum))),
            (self.sum, self.other, PowExpression((self.sum, self.other))),
            (self.sum, self.mutable_l0, PowExpression((self.sum, 0))),
            # 16:
            (self.sum, self.mutable_l1, PowExpression((self.sum, self.mon_npv))),
            (self.sum, self.mutable_l2, PowExpression((self.sum, self.l2))),
            (self.sum, self.param0, PowExpression((self.sum, 0))),
            (self.sum, self.param1, self.sum),
            # 20:
            (self.sum, self.mutable_l3, PowExpression((self.sum, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_other(self):
        tests = [
            (self.other, self.invalid, NotImplemented),
            (self.other, self.asbinary, PowExpression((self.other, self.bin))),
            (self.other, self.zero, PowExpression((self.other, 0))),
            (self.other, self.one, self.other),
            # 4:
            (self.other, self.native, PowExpression((self.other, 5))),
            (self.other, self.npv, PowExpression((self.other, self.npv))),
            (self.other, self.param, PowExpression((self.other, 6))),
            (self.other, self.param_mut, PowExpression((self.other, self.param_mut))),
            # 8:
            (self.other, self.var, PowExpression((self.other, self.var))),
            (self.other, self.mon_native, PowExpression((self.other, self.mon_native))),
            (self.other, self.mon_param, PowExpression((self.other, self.mon_param))),
            (self.other, self.mon_npv, PowExpression((self.other, self.mon_npv))),
            # 12:
            (self.other, self.linear, PowExpression((self.other, self.linear))),
            (self.other, self.sum, PowExpression((self.other, self.sum))),
            (self.other, self.other, PowExpression((self.other, self.other))),
            (self.other, self.mutable_l0, PowExpression((self.other, 0))),
            # 16:
            (self.other, self.mutable_l1, PowExpression((self.other, self.mon_npv))),
            (self.other, self.mutable_l2, PowExpression((self.other, self.l2))),
            (self.other, self.param0, PowExpression((self.other, 0))),
            (self.other, self.param1, self.other),
            # 20:
            (self.other, self.mutable_l3, PowExpression((self.other, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_mutable_l0(self):
        tests = [
            (self.mutable_l0, self.invalid, NotImplemented),
            (self.mutable_l0, self.asbinary, PowExpression((0, self.bin))),
            (self.mutable_l0, self.zero, 1),
            (self.mutable_l0, self.one, 0),
            # 4:
            (self.mutable_l0, self.native, 0),
            (self.mutable_l0, self.npv, NPV_PowExpression((0, self.npv))),
            (self.mutable_l0, self.param, 0),
            (self.mutable_l0, self.param_mut, NPV_PowExpression((0, self.param_mut))),
            # 8:
            (self.mutable_l0, self.var, PowExpression((0, self.var))),
            (self.mutable_l0, self.mon_native, PowExpression((0, self.mon_native))),
            (self.mutable_l0, self.mon_param, PowExpression((0, self.mon_param))),
            (self.mutable_l0, self.mon_npv, PowExpression((0, self.mon_npv))),
            # 12:
            (self.mutable_l0, self.linear, PowExpression((0, self.linear))),
            (self.mutable_l0, self.sum, PowExpression((0, self.sum))),
            (self.mutable_l0, self.other, PowExpression((0, self.other))),
            (self.mutable_l0, self.mutable_l0, 1),
            # 16:
            (self.mutable_l0, self.mutable_l1, PowExpression((0, self.mon_npv))),
            (self.mutable_l0, self.mutable_l2, PowExpression((0, self.l2))),
            (self.mutable_l0, self.param0, 1),
            (self.mutable_l0, self.param1, 0),
            # 20:
            (self.mutable_l0, self.mutable_l3, NPV_PowExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_mutable_l1(self):
        tests = [
            (self.mutable_l1, self.invalid, NotImplemented),
            (self.mutable_l1, self.asbinary, PowExpression((self.mon_npv, self.bin))),
            (self.mutable_l1, self.zero, PowExpression((self.mon_npv, 0))),
            (self.mutable_l1, self.one, self.mon_npv),
            # 4:
            (self.mutable_l1, self.native, PowExpression((self.mon_npv, 5))),
            (self.mutable_l1, self.npv, PowExpression((self.mon_npv, self.npv))),
            (self.mutable_l1, self.param, PowExpression((self.mon_npv, 6))),
            (
                self.mutable_l1,
                self.param_mut,
                PowExpression((self.mon_npv, self.param_mut)),
            ),
            # 8:
            (self.mutable_l1, self.var, PowExpression((self.mon_npv, self.var))),
            (
                self.mutable_l1,
                self.mon_native,
                PowExpression((self.mon_npv, self.mon_native)),
            ),
            (
                self.mutable_l1,
                self.mon_param,
                PowExpression((self.mon_npv, self.mon_param)),
            ),
            (
                self.mutable_l1,
                self.mon_npv,
                PowExpression((self.mon_npv, self.mon_npv)),
            ),
            # 12:
            (self.mutable_l1, self.linear, PowExpression((self.mon_npv, self.linear))),
            (self.mutable_l1, self.sum, PowExpression((self.mon_npv, self.sum))),
            (self.mutable_l1, self.other, PowExpression((self.mon_npv, self.other))),
            (self.mutable_l1, self.mutable_l0, PowExpression((self.mon_npv, 0))),
            # 16:
            (
                self.mutable_l1,
                self.mutable_l1,
                PowExpression((self.mon_npv, self.mon_npv)),
            ),
            (self.mutable_l1, self.mutable_l2, PowExpression((self.mon_npv, self.l2))),
            (self.mutable_l1, self.param0, PowExpression((self.mon_npv, 0))),
            (self.mutable_l1, self.param1, self.mon_npv),
            # 20:
            (self.mutable_l1, self.mutable_l3, PowExpression((self.mon_npv, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_mutable_l2(self):
        tests = [
            (self.mutable_l2, self.invalid, NotImplemented),
            (self.mutable_l2, self.asbinary, PowExpression((self.l2, self.bin))),
            (self.mutable_l2, self.zero, PowExpression((self.l2, 0))),
            (self.mutable_l2, self.one, self.l2),
            # 4:
            (self.mutable_l2, self.native, PowExpression((self.l2, 5))),
            (self.mutable_l2, self.npv, PowExpression((self.l2, self.npv))),
            (self.mutable_l2, self.param, PowExpression((self.l2, 6))),
            (self.mutable_l2, self.param_mut, PowExpression((self.l2, self.param_mut))),
            # 8:
            (self.mutable_l2, self.var, PowExpression((self.l2, self.var))),
            (
                self.mutable_l2,
                self.mon_native,
                PowExpression((self.l2, self.mon_native)),
            ),
            (self.mutable_l2, self.mon_param, PowExpression((self.l2, self.mon_param))),
            (self.mutable_l2, self.mon_npv, PowExpression((self.l2, self.mon_npv))),
            # 12:
            (self.mutable_l2, self.linear, PowExpression((self.l2, self.linear))),
            (self.mutable_l2, self.sum, PowExpression((self.l2, self.sum))),
            (self.mutable_l2, self.other, PowExpression((self.l2, self.other))),
            (self.mutable_l2, self.mutable_l0, PowExpression((self.l2, 0))),
            # 16:
            (self.mutable_l2, self.mutable_l1, PowExpression((self.l2, self.mon_npv))),
            (self.mutable_l2, self.mutable_l2, PowExpression((self.l2, self.l2))),
            (self.mutable_l2, self.param0, PowExpression((self.l2, 0))),
            (self.mutable_l2, self.param1, self.l2),
            # 20:
            (self.mutable_l2, self.mutable_l3, PowExpression((self.l2, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_param0(self):
        tests = [
            (self.param0, self.invalid, NotImplemented),
            (self.param0, self.asbinary, PowExpression((0, self.bin))),
            (self.param0, self.zero, 1),
            (self.param0, self.one, 0),
            # 4:
            (self.param0, self.native, 0),
            (self.param0, self.npv, NPV_PowExpression((0, self.npv))),
            (self.param0, self.param, 0),
            (self.param0, self.param_mut, NPV_PowExpression((0, self.param_mut))),
            # 8:
            (self.param0, self.var, PowExpression((0, self.var))),
            (self.param0, self.mon_native, PowExpression((0, self.mon_native))),
            (self.param0, self.mon_param, PowExpression((0, self.mon_param))),
            (self.param0, self.mon_npv, PowExpression((0, self.mon_npv))),
            # 12:
            (self.param0, self.linear, PowExpression((0, self.linear))),
            (self.param0, self.sum, PowExpression((0, self.sum))),
            (self.param0, self.other, PowExpression((0, self.other))),
            (self.param0, self.mutable_l0, 1),
            # 16:
            (self.param0, self.mutable_l1, PowExpression((0, self.mon_npv))),
            (self.param0, self.mutable_l2, PowExpression((0, self.l2))),
            (self.param0, self.param0, 1),
            (self.param0, self.param1, 0),
            # 20:
            (self.param0, self.mutable_l3, NPV_PowExpression((0, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_param1(self):
        tests = [
            (self.param1, self.invalid, NotImplemented),
            (self.param1, self.asbinary, PowExpression((1, self.bin))),
            (self.param1, self.zero, 1),
            (self.param1, self.one, 1),
            # 4:
            (self.param1, self.native, 1),
            (self.param1, self.npv, NPV_PowExpression((1, self.npv))),
            (self.param1, self.param, 1),
            (self.param1, self.param_mut, NPV_PowExpression((1, self.param_mut))),
            # 8:
            (self.param1, self.var, PowExpression((1, self.var))),
            (self.param1, self.mon_native, PowExpression((1, self.mon_native))),
            (self.param1, self.mon_param, PowExpression((1, self.mon_param))),
            (self.param1, self.mon_npv, PowExpression((1, self.mon_npv))),
            # 12:
            (self.param1, self.linear, PowExpression((1, self.linear))),
            (self.param1, self.sum, PowExpression((1, self.sum))),
            (self.param1, self.other, PowExpression((1, self.other))),
            (self.param1, self.mutable_l0, 1),
            # 16:
            (self.param1, self.mutable_l1, PowExpression((1, self.mon_npv))),
            (self.param1, self.mutable_l2, PowExpression((1, self.l2))),
            (self.param1, self.param0, 1),
            (self.param1, self.param1, 1),
            # 20:
            (self.param1, self.mutable_l3, NPV_PowExpression((1, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    def test_pow_mutable_l3(self):
        tests = [
            (self.mutable_l3, self.invalid, NotImplemented),
            (self.mutable_l3, self.asbinary, PowExpression((self.npv, self.bin))),
            (self.mutable_l3, self.zero, NPV_PowExpression((self.npv, 0))),
            (self.mutable_l3, self.one, self.npv),
            # 4:
            (self.mutable_l3, self.native, NPV_PowExpression((self.npv, 5))),
            (self.mutable_l3, self.npv, NPV_PowExpression((self.npv, self.npv))),
            (self.mutable_l3, self.param, NPV_PowExpression((self.npv, 6))),
            (
                self.mutable_l3,
                self.param_mut,
                NPV_PowExpression((self.npv, self.param_mut)),
            ),
            # 8:
            (self.mutable_l3, self.var, PowExpression((self.npv, self.var))),
            (
                self.mutable_l3,
                self.mon_native,
                PowExpression((self.npv, self.mon_native)),
            ),
            (
                self.mutable_l3,
                self.mon_param,
                PowExpression((self.npv, self.mon_param)),
            ),
            (self.mutable_l3, self.mon_npv, PowExpression((self.npv, self.mon_npv))),
            # 12:
            (self.mutable_l3, self.linear, PowExpression((self.npv, self.linear))),
            (self.mutable_l3, self.sum, PowExpression((self.npv, self.sum))),
            (self.mutable_l3, self.other, PowExpression((self.npv, self.other))),
            (self.mutable_l3, self.mutable_l0, NPV_PowExpression((self.npv, 0))),
            # 16:
            (self.mutable_l3, self.mutable_l1, PowExpression((self.npv, self.mon_npv))),
            (self.mutable_l3, self.mutable_l2, PowExpression((self.npv, self.l2))),
            (self.mutable_l3, self.param0, NPV_PowExpression((self.npv, 0))),
            (self.mutable_l3, self.param1, self.npv),
            # 20:
            (self.mutable_l3, self.mutable_l3, NPV_PowExpression((self.npv, self.npv))),
        ]
        self._run_cases(tests, operator.pow)
        self._run_cases(tests, operator.ipow)

    #
    #
    # NEGATION
    #
    #

    def test_neg(self):
        tests = [
            (self.invalid, NotImplemented),
            (self.asbinary, MonomialTermExpression((-1, self.bin))),
            (self.zero, 0),
            (self.one, -1),
            # 4:
            (self.native, -5),
            (self.npv, NPV_NegationExpression((self.npv,))),
            (self.param, -6),
            (self.param_mut, NPV_NegationExpression((self.param_mut,))),
            # 8:
            (self.var, MonomialTermExpression((-1, self.var))),
            (self.mon_native, self.minus_mon_native),
            (self.mon_param, self.minus_mon_param),
            (self.mon_npv, self.minus_mon_npv),
            # 12:
            (self.linear, NegationExpression((self.linear,))),
            (self.sum, NegationExpression((self.sum,))),
            (self.other, NegationExpression((self.other,))),
            (self.mutable_l0, 0),
            # 16:
            (self.mutable_l1, self.minus_mon_npv),
            (self.mutable_l2, NegationExpression((self.l2,))),
            (self.param0, 0),
            (self.param1, -1),
            # 20:
            (self.mutable_l3, self.minus_npv),
        ]
        self._run_cases(tests, operator.neg)

    def test_neg_neg(self):
        def _neg_neg(x):
            return operator.neg(operator.neg(x))

        tests = [
            (self.invalid, NotImplemented),
            (self.asbinary, MonomialTermExpression((1, self.bin))),
            (self.zero, 0),
            (self.one, 1),
            # 4:
            (self.native, 5),
            (self.npv, self.npv),
            (self.param, 6),
            (self.param_mut, self.param_mut),
            # 8:
            (self.var, MonomialTermExpression((1, self.var))),
            (self.mon_native, self.mon_native),
            (self.mon_param, self.mon_param),
            (self.mon_npv, self.mon_npv),
            # 12:
            (self.linear, self.linear),
            (self.sum, self.sum),
            (self.other, self.other),
            (self.mutable_l0, 0),
            # 16:
            (self.mutable_l1, self.mon_npv),
            (self.mutable_l2, self.l2),
            (self.param0, 0),
            (self.param1, 1),
            # 20:
            (self.mutable_l3, self.npv),
        ]
        self._run_cases(tests, _neg_neg)

    #
    #
    # ABSOLUTE VALUE
    #
    #

    def test_abs(self):
        tests = [
            (self.invalid, NotImplemented),
            (self.asbinary, AbsExpression((self.bin,))),
            (self.zero, 0),
            (self.one, 1),
            # 4:
            (self.native, 5),
            (self.npv, NPV_AbsExpression((self.npv,))),
            (self.param, 6),
            (self.param_mut, NPV_AbsExpression((self.param_mut,))),
            # 8:
            (self.var, AbsExpression((self.var,))),
            (self.mon_native, AbsExpression((self.mon_native,))),
            (self.mon_param, AbsExpression((self.mon_param,))),
            (self.mon_npv, AbsExpression((self.mon_npv,))),
            # 12:
            (self.linear, AbsExpression((self.linear,))),
            (self.sum, AbsExpression((self.sum,))),
            (self.other, AbsExpression((self.other,))),
            (self.mutable_l0, 0),
            # 16:
            (self.mutable_l1, AbsExpression((self.mon_npv,))),
            (self.mutable_l2, AbsExpression((self.l2,))),
            (self.param0, 0),
            (self.param1, 1),
            # 20:
            (self.mutable_l3, NPV_AbsExpression((self.npv,))),
        ]
        self._run_cases(tests, operator.abs)

    #
    #
    # UNARY FUNCTION
    #
    #

    def test_unary(self):
        SKIP_0 = {'log', 'log10', 'acosh'}
        SKIP_1 = {'atanh'}
        SKIP_5 = {'asin', 'acos', 'atanh'}
        SKIP_6 = SKIP_5
        for op, name, fcn in [
            (EXPR.ceil, 'ceil', math.ceil),
            (EXPR.floor, 'floor', math.floor),
            (EXPR.exp, 'exp', math.exp),
            (EXPR.log, 'log', math.log),
            (EXPR.log10, 'log10', math.log10),
            (EXPR.sqrt, 'sqrt', math.sqrt),
            (EXPR.sin, 'sin', math.sin),
            (EXPR.cos, 'cos', math.cos),
            (EXPR.tan, 'tan', math.tan),
            (EXPR.asin, 'asin', math.asin),
            (EXPR.acos, 'acos', math.acos),
            (EXPR.atan, 'atan', math.atan),
            (EXPR.sinh, 'sinh', math.sinh),
            (EXPR.cosh, 'cosh', math.cosh),
            (EXPR.tanh, 'tanh', math.tanh),
            (EXPR.asinh, 'asinh', math.asinh),
            (EXPR.acosh, 'acosh', math.acosh),
            (EXPR.atanh, 'atanh', math.atanh),
        ]:
            tests = [
                (self.invalid, NotImplemented),
                (self.asbinary, UnaryFunctionExpression((self.bin,), name, fcn)),
                (self.zero, ValueError if name in SKIP_0 else fcn(0)),
                (self.one, ValueError if name in SKIP_1 else fcn(1)),
                # 4:
                (self.native, ValueError if name in SKIP_5 else fcn(5)),
                (self.npv, NPV_UnaryFunctionExpression((self.npv,), name, fcn)),
                (self.param, ValueError if name in SKIP_6 else fcn(6)),
                (
                    self.param_mut,
                    NPV_UnaryFunctionExpression((self.param_mut,), name, fcn),
                ),
                # 8:
                (self.var, UnaryFunctionExpression((self.var,), name, fcn)),
                (
                    self.mon_native,
                    UnaryFunctionExpression((self.mon_native,), name, fcn),
                ),
                (self.mon_param, UnaryFunctionExpression((self.mon_param,), name, fcn)),
                (self.mon_npv, UnaryFunctionExpression((self.mon_npv,), name, fcn)),
                # 12:
                (self.linear, UnaryFunctionExpression((self.linear,), name, fcn)),
                (self.sum, UnaryFunctionExpression((self.sum,), name, fcn)),
                (self.other, UnaryFunctionExpression((self.other,), name, fcn)),
                (self.mutable_l0, ValueError if name in SKIP_0 else fcn(0)),
                # 16:
                (self.mutable_l1, UnaryFunctionExpression((self.l1,), name, fcn)),
                (self.mutable_l2, UnaryFunctionExpression((self.l2,), name, fcn)),
                (self.param0, ValueError if name in SKIP_0 else fcn(0)),
                (self.param1, ValueError if name in SKIP_1 else fcn(1)),
                # 20:
                (self.mutable_l3, NPV_UnaryFunctionExpression((self.npv,), name, fcn)),
            ]
            self._run_cases(tests, op)

    #
    #
    # MUTABLE SUM IADD EXPRESSIONS
    #
    #

    def test_mutable_nvp_iadd(self):
        mutable_npv = _MutableNPVSumExpression([])
        tests = [
            (mutable_npv, self.invalid, NotImplemented),
            (mutable_npv, self.asbinary, _MutableLinearExpression([self.bin])),
            (mutable_npv, self.zero, _MutableNPVSumExpression([])),
            (mutable_npv, self.one, _MutableNPVSumExpression([1])),
            # 4:
            (mutable_npv, self.native, _MutableNPVSumExpression([5])),
            (mutable_npv, self.npv, _MutableNPVSumExpression([self.npv])),
            (mutable_npv, self.param, _MutableNPVSumExpression([6])),
            (mutable_npv, self.param_mut, _MutableNPVSumExpression([self.param_mut])),
            # 8:
            (mutable_npv, self.var, _MutableLinearExpression([self.var])),
            (mutable_npv, self.mon_native, _MutableLinearExpression([self.mon_native])),
            (mutable_npv, self.mon_param, _MutableLinearExpression([self.mon_param])),
            (mutable_npv, self.mon_npv, _MutableLinearExpression([self.mon_npv])),
            # 12:
            (mutable_npv, self.linear, _MutableLinearExpression(self.linear.args)),
            (mutable_npv, self.sum, _MutableSumExpression(self.sum.args)),
            (mutable_npv, self.other, _MutableSumExpression([self.other])),
            (mutable_npv, self.mutable_l0, _MutableNPVSumExpression([])),
            # 16:
            (mutable_npv, self.mutable_l1, _MutableLinearExpression([self.l1])),
            (mutable_npv, self.mutable_l2, _MutableSumExpression(self.l2.args)),
            (mutable_npv, self.param0, _MutableNPVSumExpression([])),
            (mutable_npv, self.param1, _MutableNPVSumExpression([1])),
            # 20:
            (mutable_npv, self.mutable_l3, _MutableNPVSumExpression([self.npv])),
        ]
        self._run_iadd_cases(tests, operator.iadd)

        mutable_npv = _MutableNPVSumExpression([10])
        tests = [
            (mutable_npv, self.invalid, NotImplemented),
            (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.bin])),
            (mutable_npv, self.zero, _MutableNPVSumExpression([10])),
            (mutable_npv, self.one, _MutableNPVSumExpression([11])),
            # 4:
            (mutable_npv, self.native, _MutableNPVSumExpression([15])),
            (mutable_npv, self.npv, _MutableNPVSumExpression([10, self.npv])),
            (mutable_npv, self.param, _MutableNPVSumExpression([16])),
            (
                mutable_npv,
                self.param_mut,
                _MutableNPVSumExpression([10, self.param_mut]),
            ),
            # 8:
            (mutable_npv, self.var, _MutableLinearExpression([10, self.var])),
            (
                mutable_npv,
                self.mon_native,
                _MutableLinearExpression([10, self.mon_native]),
            ),
            (
                mutable_npv,
                self.mon_param,
                _MutableLinearExpression([10, self.mon_param]),
            ),
            (mutable_npv, self.mon_npv, _MutableLinearExpression([10, self.mon_npv])),
            # 12:
            (
                mutable_npv,
                self.linear,
                _MutableLinearExpression([10] + self.linear.args),
            ),
            (mutable_npv, self.sum, _MutableSumExpression([10] + self.sum.args)),
            (mutable_npv, self.other, _MutableSumExpression([10, self.other])),
            (mutable_npv, self.mutable_l0, _MutableNPVSumExpression([10])),
            # 16:
            (mutable_npv, self.mutable_l1, _MutableLinearExpression([10, self.l1])),
            (mutable_npv, self.mutable_l2, _MutableSumExpression([10] + self.l2.args)),
            (mutable_npv, self.param0, _MutableNPVSumExpression([10])),
            (mutable_npv, self.param1, _MutableNPVSumExpression([11])),
            # 20:
            (mutable_npv, self.mutable_l3, _MutableNPVSumExpression([10, self.npv])),
        ]
        self._run_iadd_cases(tests, operator.iadd)

    def test_mutable_lin_iadd(self):
        mutable_lin = _MutableLinearExpression([])
        tests = [
            (mutable_lin, self.invalid, NotImplemented),
            (mutable_lin, self.asbinary, _MutableLinearExpression([self.bin])),
            (mutable_lin, self.zero, _MutableLinearExpression([])),
            (mutable_lin, self.one, _MutableLinearExpression([1])),
            # 4:
            (mutable_lin, self.native, _MutableLinearExpression([5])),
            (mutable_lin, self.npv, _MutableLinearExpression([self.npv])),
            (mutable_lin, self.param, _MutableLinearExpression([6])),
            (mutable_lin, self.param_mut, _MutableLinearExpression([self.param_mut])),
            # 8:
            (mutable_lin, self.var, _MutableLinearExpression([self.var])),
            (mutable_lin, self.mon_native, _MutableLinearExpression([self.mon_native])),
            (mutable_lin, self.mon_param, _MutableLinearExpression([self.mon_param])),
            (mutable_lin, self.mon_npv, _MutableLinearExpression([self.mon_npv])),
            # 12:
            (mutable_lin, self.linear, _MutableLinearExpression(self.linear.args)),
            (mutable_lin, self.sum, _MutableSumExpression(self.sum.args)),
            (mutable_lin, self.other, _MutableSumExpression([self.other])),
            (mutable_lin, self.mutable_l0, _MutableLinearExpression([])),
            # 16:
            (mutable_lin, self.mutable_l1, _MutableLinearExpression([self.l1])),
            (mutable_lin, self.mutable_l2, _MutableSumExpression(self.l2.args)),
            (mutable_lin, self.param0, _MutableLinearExpression([])),
            (mutable_lin, self.param1, _MutableLinearExpression([1])),
            # 20:
            (mutable_lin, self.mutable_l3, _MutableLinearExpression([self.npv])),
        ]
        self._run_iadd_cases(tests, operator.iadd)

        mutable_lin = _MutableLinearExpression([self.bin])
        tests = [
            (mutable_lin, self.invalid, NotImplemented),
            (
                mutable_lin,
                self.asbinary,
                _MutableLinearExpression([self.bin, self.bin]),
            ),
            (mutable_lin, self.zero, _MutableLinearExpression([self.bin])),
            (mutable_lin, self.one, _MutableLinearExpression([self.bin, 1])),
            # 4:
            (mutable_lin, self.native, _MutableLinearExpression([self.bin, 5])),
            (mutable_lin, self.npv, _MutableLinearExpression([self.bin, self.npv])),
            (mutable_lin, self.param, _MutableLinearExpression([self.bin, 6])),
            (
                mutable_lin,
                self.param_mut,
                _MutableLinearExpression([self.bin, self.param_mut]),
            ),
            # 8:
            (mutable_lin, self.var, _MutableLinearExpression([self.bin, self.var])),
            (
                mutable_lin,
                self.mon_native,
                _MutableLinearExpression([self.bin, self.mon_native]),
            ),
            (
                mutable_lin,
                self.mon_param,
                _MutableLinearExpression([self.bin, self.mon_param]),
            ),
            (
                mutable_lin,
                self.mon_npv,
                _MutableLinearExpression([self.bin, self.mon_npv]),
            ),
            # 12:
            (
                mutable_lin,
                self.linear,
                _MutableLinearExpression([self.bin] + self.linear.args),
            ),
            (mutable_lin, self.sum, _MutableSumExpression([self.bin] + self.sum.args)),
            (mutable_lin, self.other, _MutableSumExpression([self.bin, self.other])),
            (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.bin])),
            # 16:
            (
                mutable_lin,
                self.mutable_l1,
                _MutableLinearExpression([self.bin, self.l1]),
            ),
            (
                mutable_lin,
                self.mutable_l2,
                _MutableSumExpression([self.bin] + self.l2.args),
            ),
            (mutable_lin, self.param0, _MutableLinearExpression([self.bin])),
            (mutable_lin, self.param1, _MutableLinearExpression([self.bin, 1])),
            # 20:
            (
                mutable_lin,
                self.mutable_l3,
                _MutableLinearExpression([self.bin, self.npv]),
            ),
        ]
        self._run_iadd_cases(tests, operator.iadd)

    def test_mutable_sum_iadd(self):
        mutable_sum = _MutableSumExpression([])
        tests = [
            (mutable_sum, self.invalid, NotImplemented),
            (mutable_sum, self.asbinary, _MutableSumExpression([self.bin])),
            (mutable_sum, self.zero, _MutableSumExpression([])),
            (mutable_sum, self.one, _MutableSumExpression([1])),
            # 4:
            (mutable_sum, self.native, _MutableSumExpression([5])),
            (mutable_sum, self.npv, _MutableSumExpression([self.npv])),
            (mutable_sum, self.param, _MutableSumExpression([6])),
            (mutable_sum, self.param_mut, _MutableSumExpression([self.param_mut])),
            # 8:
            (mutable_sum, self.var, _MutableSumExpression([self.var])),
            (mutable_sum, self.mon_native, _MutableSumExpression([self.mon_native])),
            (mutable_sum, self.mon_param, _MutableSumExpression([self.mon_param])),
            (mutable_sum, self.mon_npv, _MutableSumExpression([self.mon_npv])),
            # 12:
            (mutable_sum, self.linear, _MutableSumExpression([self.linear])),
            (mutable_sum, self.sum, _MutableSumExpression(self.sum.args)),
            (mutable_sum, self.other, _MutableSumExpression([self.other])),
            (mutable_sum, self.mutable_l0, _MutableSumExpression([])),
            # 16:
            (mutable_sum, self.mutable_l1, _MutableSumExpression([self.l1])),
            (mutable_sum, self.mutable_l2, _MutableSumExpression(self.l2.args)),
            (mutable_sum, self.param0, _MutableSumExpression([])),
            (mutable_sum, self.param1, _MutableSumExpression([1])),
            # 20:
            (mutable_sum, self.mutable_l3, _MutableSumExpression([self.npv])),
        ]
        self._run_iadd_cases(tests, operator.iadd)

        mutable_sum = _MutableSumExpression([self.other])
        tests = [
            (mutable_sum, self.invalid, NotImplemented),
            (mutable_sum, self.asbinary, _MutableSumExpression([self.other, self.bin])),
            (mutable_sum, self.zero, _MutableSumExpression([self.other])),
            (mutable_sum, self.one, _MutableSumExpression([self.other, 1])),
            # 4:
            (mutable_sum, self.native, _MutableSumExpression([self.other, 5])),
            (mutable_sum, self.npv, _MutableSumExpression([self.other, self.npv])),
            (mutable_sum, self.param, _MutableSumExpression([self.other, 6])),
            (
                mutable_sum,
                self.param_mut,
                _MutableSumExpression([self.other, self.param_mut]),
            ),
            # 8:
            (mutable_sum, self.var, _MutableSumExpression([self.other, self.var])),
            (
                mutable_sum,
                self.mon_native,
                _MutableSumExpression([self.other, self.mon_native]),
            ),
            (
                mutable_sum,
                self.mon_param,
                _MutableSumExpression([self.other, self.mon_param]),
            ),
            (
                mutable_sum,
                self.mon_npv,
                _MutableSumExpression([self.other, self.mon_npv]),
            ),
            # 12:
            (
                mutable_sum,
                self.linear,
                _MutableSumExpression([self.other, self.linear]),
            ),
            (
                mutable_sum,
                self.sum,
                _MutableSumExpression([self.other] + self.sum.args),
            ),
            (mutable_sum, self.other, _MutableSumExpression([self.other, self.other])),
            (mutable_sum, self.mutable_l0, _MutableSumExpression([self.other])),
            # 16:
            (
                mutable_sum,
                self.mutable_l1,
                _MutableSumExpression([self.other, self.l1]),
            ),
            (
                mutable_sum,
                self.mutable_l2,
                _MutableSumExpression([self.other] + self.l2.args),
            ),
            (mutable_sum, self.param0, _MutableSumExpression([self.other])),
            (mutable_sum, self.param1, _MutableSumExpression([self.other, 1])),
            # 20:
            (
                mutable_sum,
                self.mutable_l3,
                _MutableSumExpression([self.other, self.npv]),
            ),
        ]
        self._run_iadd_cases(tests, operator.iadd)


class TestExpressionGeneration_Misc(BaseNumeric, unittest.TestCase):
    def test_enable_optimizations(self):
        enable_expression_optimizations(zero=False, one=False)
        self.assertEqual(_zero_one_optimizations, set())

        enable_expression_optimizations(zero=True, one=False)
        self.assertEqual(_zero_one_optimizations, {0})

        enable_expression_optimizations(zero=True, one=True)
        self.assertEqual(_zero_one_optimizations, {0, 1})

        enable_expression_optimizations(zero=False, one=True)
        self.assertEqual(_zero_one_optimizations, {1})

        enable_expression_optimizations()
        self.assertEqual(_zero_one_optimizations, {1})

    def test_unreachable_dispatchers(self):
        # The following dispatchers cannot be triggered through the
        # normal NumericValue operator overloads with regular Pyomo
        # classes.  We will declare a "fake" type that as all the
        # algebraic operator overloads, but reports itself as a
        # "non-numeric type"
        class CustomAsNumeric(NumericValue):
            def __init__(self, val):
                self._value = val

            def is_numeric_type(self):
                return False

            def as_numeric(self):
                return self._value

        obj = CustomAsNumeric(self.var)
        e = abs(obj)
        assertExpressionsEqual(self, AbsExpression((self.var,)), e)

        e = obj**2
        assertExpressionsEqual(self, PowExpression((self.var, 2)), e)

        e = obj + obj
        assertExpressionsEqual(self, LinearExpression((self.var, self.var)), e)

    def test_categorize_arg_type(self):
        class CustomAsNumeric(NumericValue):
            def __init__(self, val):
                self._value = val

            def is_numeric_type(self):
                return False

            def as_numeric(self):
                return self._value

        class CustomNumeric(NumericValue):
            def is_potentially_variable(self):
                return True

        saved_known_arg_types = dict(_known_arg_types)
        saved_native_numeric_types = set(native_numeric_types)
        saved_native_types = set(native_types)
        try:
            _known_arg_types.clear()
            native_types.discard(int)
            native_numeric_types.discard(int)
            self.assertIs(_categorize_arg_type(10), ARG_TYPE.NATIVE)
            self.assertIn(int, _known_arg_types)
            self.assertIn(int, native_numeric_types)
            # This checks against the native_numeric_types cache...
            _known_arg_types.clear()
            self.assertIs(_categorize_arg_type(10), ARG_TYPE.NATIVE)
            # This checks against the _known_arg_types cache...
            self.assertIs(_categorize_arg_type(10), ARG_TYPE.NATIVE)

            self.assertIs(_categorize_arg_type(self.invalid), ARG_TYPE.INVALID)
            self.assertIs(_categorize_arg_type(self.asbinary), ARG_TYPE.ASNUMERIC)
            self.assertIs(_categorize_arg_type(self.zero), ARG_TYPE.NATIVE)
            self.assertIs(_categorize_arg_type(self.one), ARG_TYPE.NATIVE)
            self.assertIs(_categorize_arg_type(self.native), ARG_TYPE.NATIVE)
            self.assertIs(_categorize_arg_type(self.npv), ARG_TYPE.NPV)
            self.assertIs(_categorize_arg_type(self.param), ARG_TYPE.PARAM)
            self.assertIs(_categorize_arg_type(self.param0), ARG_TYPE.PARAM)
            self.assertIs(_categorize_arg_type(self.param1), ARG_TYPE.PARAM)
            self.assertIs(_categorize_arg_type(self.param_mut), ARG_TYPE.PARAM)
            self.assertIs(_categorize_arg_type(self.var), ARG_TYPE.VAR)
            self.assertIs(_categorize_arg_type(self.mon_native), ARG_TYPE.MONOMIAL)
            self.assertIs(_categorize_arg_type(self.mon_param), ARG_TYPE.MONOMIAL)
            self.assertIs(_categorize_arg_type(self.mon_npv), ARG_TYPE.MONOMIAL)
            self.assertIs(_categorize_arg_type(self.linear), ARG_TYPE.LINEAR)
            self.assertIs(_categorize_arg_type(self.sum), ARG_TYPE.SUM)
            self.assertIs(_categorize_arg_type(self.other), ARG_TYPE.OTHER)
            self.assertIs(_categorize_arg_type(self.mutable_l0), ARG_TYPE.MUTABLE)
            self.assertIs(_categorize_arg_type(self.mutable_l1), ARG_TYPE.MUTABLE)
            self.assertIs(_categorize_arg_type(self.mutable_l2), ARG_TYPE.MUTABLE)
            self.assertIs(_categorize_arg_type(self.mutable_l3), ARG_TYPE.MUTABLE)

            # Other interesting tests (partially for coverage)
            self.assertIs(_categorize_arg_type(self.m), ARG_TYPE.INVALID)
            self.assertIs(_categorize_arg_type(CustomAsNumeric(10)), ARG_TYPE.ASNUMERIC)
            self.assertIs(_categorize_arg_type(CustomNumeric()), ARG_TYPE.OTHER)

        finally:
            _known_arg_types.clear()
            _known_arg_types.update(saved_known_arg_types)
            native_numeric_types.clear()
            native_numeric_types.update(saved_native_numeric_types)
            native_types.clear()
            native_types.update(saved_native_types)
