Skip to content

modeling: floor and ceil have no symbolic node, so a GAMS model that uses them cannot be imported #1237

Description

@jkitchin

Summary

floor and ceil are absent from the expression IR entirely. They are not in
the Rust MathFunc enum (crates/discopt-core/src/expr.rs:50), not in
modeling/core.py, and not exported anywhere:

discopt.floor -> AttributeError: module 'discopt' has no attribute 'floor'
dm.floor      -> AttributeError: module 'discopt.modeling' has no attribute 'floor'
dm.ceil       -> AttributeError

The only occurrences in the tree are constant folding in the GAMS parser
(python/discopt/modeling/gams_parser.py:1527-1530), which evaluates
ceil(2.3) when the argument is already a literal. There is no path that
produces a symbolic node over a variable.

Spun out of #1233 (CHECKS_EXECUTED 10; the three probes above plus the
min/max set that became the sibling issue).

Why this is worth its own issue

#1233's comparison paragraph names them:

For comparison, Pyomo exports its full intrinsic set (sqrt, log, log10,
exp, the trig and hyperbolic families, ceil, floor) from
pyomo.environ.

but its Suggested fix listed only the 13 names that already existed in
core.py, so the PR closing it (branch claude/awesome-goodall-kjoq0d) did not
add these — every other name in that comparison is now exported, these two are
not, because there is nothing to export.

The concrete consequence is an import gap, not just an ergonomics gap. GAMS
is the only route to a full-license BARON (the bundled /Applications/AMPL/baron
is demo-limited to 10 variables — see CLAUDE.md's three-way head-to-head
section), and minlplib.org/gms/ instances that use floor/ceil over a
variable have no import path today. Worth measuring how many corpus instances
that is before deciding priority — that is the entry experiment below.

Why it is not a small change

floor/ceil are discontinuous, which puts them in a different class from
every intrinsic currently in the IR. Each of these needs a decision, and getting
any of them wrong is a false-certificate risk rather than a slowdown
(CLAUDE.md §1):

  1. Relaxation. floor(x) over a box [l, u] has a valid convex
    envelope, but it is not the interval-arithmetic result the existing
    elementwise machinery would produce. The standard treatment is a MILP
    reformulation (floor(x) = z, z integer, z <= x <= z + 1 - eps), which
    changes the variable set, not just the DAG.
  2. Derivatives. The AD tape has no meaningful gradient here. The NLP
    relaxation would need the reformulated integer variable rather than the
    function node.
  3. FBBT. crates/discopt-core/src/presolve/fbbt.rs would need forward and
    backward propagation rules for a step function.
  4. .nl / GAMS / LP writers. .nl has opcodes for both; the writers would
    need to emit them, and the round-trip test corpus would need instances that
    exercise them.

Entry experiment (run before implementing, per CLAUDE.md §4)

Hypothesis: floor/ceil over a variable appear in enough of the MINLPLib
corpus to justify the reformulation work.

Measurement: grep ~/Dropbox/projects/discopt-minlp-benchmark/minlplib/ for
.gms instances containing floor(/ceil( with a non-literal argument, and
count how many currently fail to import. Report the count and the instance names.

Kill criterion: if the count is ~0, close this as "not reachable from the
corpus" and instead make the refusal explicit — today dm.floor is an
AttributeError from a missing name, which reads as an oversight rather than a
deliberate exclusion. A named NotImplementedError explaining that
discontinuous intrinsics need a MILP reformulation would be the whole fix, and
is worth doing either way.

Scope note

If implemented, sign is worth reviewing at the same time: it is in the IR
(MathFunc::Sign) and is equally discontinuous, so whatever treatment
floor/ceil get should either match it or explain why it differs.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions