Summary
python/discopt/modeling/core.py defines 19 symbolic intrinsic functions
(lines 1505–1738), but only 6 are re-exported from the top-level discopt
namespace. The other 13 are reachable only as
from discopt.modeling.core import <name>.
Measured at runtime on fix/lp-std-form-consolidation (worktree wt-stdform):
top-level : exp log sqrt sin cos tan
core-only : abs_ acos acosh asin asinh atan atanh cosh log10 log1p log2 sinh tanh
So from discopt import sqrt works and from discopt import tanh raises
AttributeError / ImportError.
Why this is a hard block rather than a papercut
Expression.__array_ufunc__ = None (modeling/core.py:619) means a user cannot
fall back on NumPy:
>>> np.tanh(x)
TypeError: operand 'Variable' does not support ufuncs (__array_ufunc__=None)
That opt-out is the right call on its own terms — it stops NumPy from silently
returning an object array of expressions — but combined with the missing export
it leaves no discoverable way to write tanh(x) in a model. The user has to know
that discopt.modeling.core exists and that the function lives there.
tanh in particular is a common activation, and discopt.nn's
FullSpaceFormulation / ReducedSpaceFormulation handle smooth activations, so
a model that embeds a tanh network is a natural thing to write by hand.
For comparison, Pyomo exports its full intrinsic set (sqrt, log, log10,
exp, the trig and hyperbolic families, ceil, floor) from pyomo.environ.
Reproduction
import numpy as np
from discopt import Model
m = Model()
x = m.continuous("x", lb=0.1, ub=2.0)
from discopt import tanh # AttributeError / ImportError
np.tanh(x) # TypeError: does not support ufuncs
from discopt.modeling.core import tanh
tanh(x) # works -> FunctionCall
Suggested fix
Extend the re-export block in python/discopt/__init__.py (currently around
lines 181–193, which lists cos, exp, log, sin, sqrt, tan) to cover
the remaining 13: abs_, acos, acosh, asin, asinh, atan, atanh,
cosh, log10, log1p, log2, sinh, tanh.
Two things to decide while doing it:
abs_ naming. The trailing underscore avoids shadowing the builtin. Worth
confirming that is still the intent before promoting it to the top level,
since from discopt import * would then introduce abs_ alongside abs.
- Lazy-import cost.
__init__.py has an explicit note about import cost and
a deferred-import list; the intrinsics are plain functions in
modeling/core.py, which is already imported for Model, so this should add
nothing measurable — but the added names should go in the same block as the
existing six rather than triggering a new module import.
Test
A regression test asserting every public intrinsic defined in
modeling/core.py is reachable from the top-level namespace, so the two lists
cannot drift apart again. Enumerating them by introspection rather than by a
hardcoded name list is what keeps a future addition from repeating this.
Verification of the claims above
Three runtime checks executed (CHECKS_EXECUTED 3): np.tanh(x) raises
TypeError; discopt.tanh raises AttributeError; discopt.modeling.core.tanh(x)
returns a FunctionCall.
Summary
python/discopt/modeling/core.pydefines 19 symbolic intrinsic functions(lines 1505–1738), but only 6 are re-exported from the top-level
discoptnamespace. The other 13 are reachable only as
from discopt.modeling.core import <name>.Measured at runtime on
fix/lp-std-form-consolidation(worktreewt-stdform):So
from discopt import sqrtworks andfrom discopt import tanhraisesAttributeError/ImportError.Why this is a hard block rather than a papercut
Expression.__array_ufunc__ = None(modeling/core.py:619) means a user cannotfall back on NumPy:
That opt-out is the right call on its own terms — it stops NumPy from silently
returning an object array of expressions — but combined with the missing export
it leaves no discoverable way to write
tanh(x)in a model. The user has to knowthat
discopt.modeling.coreexists and that the function lives there.tanhin particular is a common activation, anddiscopt.nn'sFullSpaceFormulation/ReducedSpaceFormulationhandle smooth activations, soa model that embeds a
tanhnetwork is a natural thing to write by hand.For comparison, Pyomo exports its full intrinsic set (
sqrt,log,log10,exp, the trig and hyperbolic families,ceil,floor) frompyomo.environ.Reproduction
Suggested fix
Extend the re-export block in
python/discopt/__init__.py(currently aroundlines 181–193, which lists
cos,exp,log,sin,sqrt,tan) to coverthe remaining 13:
abs_,acos,acosh,asin,asinh,atan,atanh,cosh,log10,log1p,log2,sinh,tanh.Two things to decide while doing it:
abs_naming. The trailing underscore avoids shadowing the builtin. Worthconfirming that is still the intent before promoting it to the top level,
since
from discopt import *would then introduceabs_alongsideabs.__init__.pyhas an explicit note about import cost anda deferred-import list; the intrinsics are plain functions in
modeling/core.py, which is already imported forModel, so this should addnothing measurable — but the added names should go in the same block as the
existing six rather than triggering a new module import.
Test
A regression test asserting every public intrinsic defined in
modeling/core.pyis reachable from the top-level namespace, so the two listscannot drift apart again. Enumerating them by introspection rather than by a
hardcoded name list is what keeps a future addition from repeating this.
Verification of the claims above
Three runtime checks executed (
CHECKS_EXECUTED 3):np.tanh(x)raisesTypeError;discopt.tanhraisesAttributeError;discopt.modeling.core.tanh(x)returns a
FunctionCall.