These instructions apply to the whole repository.
- Keep this file curated: fold new lessons into the most specific existing section, replace obsolete detail, and prefer reusable guidance.
- GDPlib is a Python model library for Generalized Disjunctive Programming examples and benchmarks built with Pyomo.
- GDPlib is licensed under the BSD 3-clause license. Keep new contributions compatible with that license.
- Supported Python versions are 3.10, 3.11, and 3.12. Keep
pyproject.toml,setup.py, CI, and environment manifests aligned when changing support. - Treat optimization solvers as optional external tools. Do not make model construction, imports, or default tests require GAMS, IPOPT, or another solver unless the task explicitly asks for solver-specific behavior.
- Direct Gurobi Python access is available through the optional Pixi
environment
gurobi, which addsgurobipywithout making it part of the default development environment. Keep Gurobi license paths local; documentGRB_LICENSE_FILErather than hard-coding machine-specific paths. - Models often represent chemical-engineering or operations-research optimization problems. Preserve the model's reference source and benchmarking intent when making changes.
- Prefer the committed Pixi environment for reproducible Linux development
(
linux-64):pixi install pixi run test pixi run lint - When Pixi is available, run tests, lint, and documentation generators through
pixi run ...so the Pixi-managed.venvsupplies the Python version and dependencies. Avoid using the system Python for PR verification unless Pixi is unavailable and the fallback is called out explicitly. - If
pixiis not onPATH, check whether a local Pixi binary exists (for example$HOME/.pixi/bin/pixi) before falling back to the pip workflow. - The committed Pixi support surface is exactly the platform list in
pixi.tomland the matching entries inpixi.lock; it is currentlylinux-64. Keep README,pixi.toml, andpixi.lockaligned when changing that policy. - On macOS or Windows, use the pip workflow below unless the task explicitly expands Pixi platform support.
- Do not add Pixi platforms casually. Adding
osx-64,osx-arm64,win-64, or another platform must also regeneratepixi.lockand verifypixi install,pixi run test, andpixi run linton that platform before committing manifest or lock-file changes. If the platform cannot be verified, leave the manifest and lock unchanged and document the work as follow-up. - The legacy pip workflow is still supported:
pip install -r requirements.txt pip install -r requirements-dev.txt pip install -e . - Do not commit generated environments, caches, coverage output, or benchmark output.
- Run targeted tests for the code you change, then run broader tests when practical.
- Default test command:
pytest tests/ -v --tb=short
- For model import and construction changes, include:
pytest tests/test_module_imports.py -v --tb=short
- When changing
build_model()tests, make unexpected construction failures fail the test. Inspect builder signatures before calling models that may require arguments; only skip required-argument builders or unavailable optional solvers. Do not catchExceptionorTypeErrorfrombuild_model()and turn it into a skip. - For line-ending-heavy PRs, inspect
git diff --ignore-cr-at-eol, still rungit diff --check, and avoid unrelated normalization. - For GitHub issue or PR inspection through
gh, prefer explicit--jsonfield lists when default views request deprecated GraphQL fields such as Projects ClassicprojectCards; this can affect plain issue or PR views such asgh issue view --commentsandgh pr view --web=false. GitHub rejects approvals from the PR author; submit aCOMMENTreview instead and note that an eligible reviewer is still required. - Match CI formatting and linting:
black -S -C --target-version py310 --check --diff . flake8 gdplib/ --count --select=E9,F63,F7,F82 --show-source --statistics flake8 gdplib/ --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics typos --config ./.github/workflows/typos.toml - The second flake8 pass is an
--exit-zerostyle inventory. Treat the command exit status as authoritative, and keep focused PRs limited to new Black, critical flake8, or typos failures unless broader cleanup is requested.
- Follow the style of the surrounding module, while keeping changed Python code compatible with the repository Black and flake8 checks.
- Use type hints where they clarify public helper APIs, but do not introduce large annotation-only refactors in model files.
- Keep docstrings accurate. Use NumPy/SciPy-style
ParametersandReturnssections only for actual callable signatures; document constructor-created model attributes as implementation notes, module documentation, or model README content. - For new or substantially changed models, include relevant mathematical formulation details, usage examples, and source references in the model README or module documentation.
- For model documentation audits, prefer documentation, module docstrings, and inline comments over behavior changes. Do not commit source PDFs, extracted paper text, or other local research artifacts unless they are intentional package data.
- Trace units, scaling, constants, and source references to the paper or a
clearly named related implementation. If a quantity is only known in source
scaling, label it
[source scale]or[dimensionless]; distinguish equivalent formulations from merely useful references. - If
typosflags a correct domain/project name, add the exact accepted term to.github/workflows/typos.tomlrather than misspelling, avoiding the correct name, or renaming a public API/model component solely for spell-check. - Use
### Solutionconsistently for README solution sections. For a single verified objective value, prefer:Best known objective value: <value> (optimal). For multiple formulations or instances, useBest known objective values:followed by one entry per case. If a value is not proven optimal, say "best known" rather than "optimal" and include the solver status or gap when known. - When adding or changing documented solution values or model-specific benchmark seeds such as GDPOpt initial disjunct sets, try to reproduce them with the model's existing solve path or a focused Pyomo script, preferably inside the Pixi environment. Record the model instance/formulation, transformation, solver interface, solver, termination condition, objective value, discrete selections, and any gap or infeasibility evidence in the PR or issue discussion.
- Keep solver-backed solution verification separate from default model construction tests. GAMS, BARON, IPOPT, and similar tools are optional, so do not make default imports, model construction, or CI tests depend on them unless the task explicitly asks for solver-specific behavior.
- If a model cannot be solved because of a missing solver, timeout, infeasible result, or runtime error, search existing GitHub issues first. Update the relevant issue when one exists; otherwise create a new issue with the exact command, environment, solver stack, termination condition or traceback, and model/formulation attempted.
- When escalating Pyomo or solver behavior upstream, include a compact MWE, exact environment and solver roles, and links to the affected GDPlib issue, PR, and benchmark comment. Keep distinct root causes in separate upstream issues but cross-link related evidence. Prefer a solver-free or open-solver MWE when it can isolate the behavior; otherwise keep licensed-solver evidence as downstream context rather than the only reproduction. Use the upstream project's issue template when one exists.
- A future structured solution-record format is tracked in #105. The intended direction is inspired by MINLPLib solution metadata: objective sense/value, best primal and dual bounds, infeasibility, solver and solve metadata, model instance or formulation, and optionally nonzero primal variable values.
- For Pyomo code, follow the import style already used by the local module.
- When fixing Pyomo or Pyomo.GDP deprecations, keep the change narrowly tied to the deprecated API. Do not bundle unrelated warning classes, numerical initialization changes, or formulation changes into the same PR unless they are required for correctness.
- Set
BooleanVarand GDPindicator_varvalues withTrue/False, not numeric1/0. When associating aBooleanVarwith a disjunct indicator, use the indicator's associated binary variable instead of the Booleanindicator_varitself. - For GDPOpt dispatcher calls through
SolverFactory("gdpopt").solve(...), usealgorithm=...instead of the deprecated top-levelstrategy=.... Verify current Pyomo option names in the committed environment before updating less common GDPOpt options. For strategy-specific GDPOpt options, checkSolverFactory(strategy).CONFIG()or validate kwargs withCONFIG().set_value(...), and gate options per strategy because GDPOpt variants do not all share the same config surface. - For ordered Pyomo
Setpositional access, useset.at(index)when the intent is positional lookup. Do not rely on deprecatedset[index]behavior. - Add focused warning-regression tests for deprecation cleanup. Prefer capturing Pyomo warning logs for the smallest relevant build or transform path, and assert against the specific deprecated warning patterns being removed. Do not make tests fail on unrelated existing warnings; track those separately in the relevant model issue.
- If a warning cleanup touches model construction, GDP transformations, or solver-facing expressions, compare the affected benchmark instances against existing baseline artifacts when practical. Record objective values, termination conditions, bounds, and unchanged failures in the PR or issue discussion, but do not commit generated benchmark outputs.
- Treat Pyomo/FBBT runtime warnings as triage signals, not automatic model defects. First fix invalid domains, missing finite bounds, or unsafe algebra in the model. If the model has valid bounds and the warning is isolated to Pyomo internals such as scalar interval arithmetic, report a focused upstream MWE instead of hiding it with unrelated model changes.
- Use the committed benchmark CLI for solver-backed campaigns:
pixi run gdplib-benchmark preflight pixi run gdplib-benchmark run pixi run gdplib-benchmark warnings
- Treat
benchmark_cases/*.csvandbenchmark_cases/*.jsonas reusable, version-controlled campaign inputs, not benchmark results. Commit a case file only when it captures a generally useful campaign configuration. - Do not commit generated benchmark artifacts. Keep run outputs under ignored
paths such as
gdplib/*/benchmark_result/,benchmark_runs/, andbenchmark_summary/. Record important results in the relevant PR or issue discussion instead. - Benchmark and reporting helpers that redirect solver output must restore
stdout/stderreven when solves fail. - Preflight before long campaigns. Start with small, bounded runs using
explicit
--instances,--strategies,--timelimit,--solver-profile, and--run-idvalues, then broaden coverage once failures are understood. - GAMS-backed benchmark solves should use the committed default
option optcr=1e-6so solver incumbents and bounds are compared at a consistent relative gap. If a looser exploratory gap is intentional, label it explicitly in the run id and result discussion. - If a benchmark run writes per-case JSON/log artifacts but fails while
generating summaries, inspect the result files and
failures.*before changing the model. Use a focused Pyomo/GDPopt script for clean solver-backed verification when the summary failure is outside the model change. - Choose solvers according to transformed model class and benchmark goal: use LP/MIP solvers such as Gurobi or HiGHS for direct MIP reformulations, IPOPT or GAMS IPOPTH for local NLP roles, DICOPT for local MINLP roles, and BARON only when global evidence is intended. If Pyomo's direct Gurobi interface is unavailable or rejects the transformed model, use the GAMS/Gurobi profile and say why.
- For linear GDP benchmarks, verify the transformed class when practical
(binary variables plus linear objectives/constraints are usually enough).
Compare direct
gdp.bigm/gdp.hulland GDPOpt LOA/GLOA/RIC before trying slower strategies. Avoidgdpopt.enumeratefor large-disjunction or ordered-location families unless enumeration itself is the benchmark goal: Pyomo currently materializes the full discrete solution list before enforcingtime_limit(Pyomo/pyomo#3953), so timeout settings may not protect memory. Direct Hull through GAMS/Gurobi can be good quick evidence for large GDPs, but report the actual GAMSoptcr/gap so a 1% certificate is not mistaken for a 1e-6 run. - Interpret GDPOpt results conservatively on nonconvex models. Do not treat
gdpopt.loaoptimal,LB = UB, or missing-bound results as rigorous global certificates unless convexity and valid OA bounds are established; cross-check against GLOA, RIC, direct transformed solves, or explicit feasible points. - For
gdpopt.lbbfailures, separate model issues from GDPOpt control-flow or solver-role issues. Known patterns include linear relaxed-node subproblems being routed tominlp_solver, MINLP-only solvers receiving continuous/MIP nodes, and time-limit finalization failures such as Pyomo/pyomo#3941. Isolate with role-solver probes or MWEs before changing the model, and do not assume LOA/GLOA/RIC share the same routing behavior. - Trust solver logs over wrapper summaries when they disagree. If a GAMS/Pyomo wrapper reports an objective or bound but the solver log says no incumbent was found, report the solver-log status and note the wrapper discrepancy.
- When reporting benchmark results, include the exact command, run id, instance, strategy, solver interface, GAMS solver and GDPOpt role solvers, time limit, termination condition, objective, primal/dual bounds, gap or infeasibility evidence, and failure log path or traceback.
- Keep specialized diagnostics focused: use BARON
CompIISwith symbolic GAMS labels for infeasibility work, keep external solver handoff packages under ignored paths such as/tmp, and usegdplib-benchmark warningswith the narrowest useful mode to capture warning/deprecation evidence.
README.mdandgdplib/*/model_size_report.mdare generated bygenerate_model_size_report.pyand should remain reproducible from that script. Do not ignore generated model-size reports; commit changed report outputs together with generator changes.- When changing
generate_model_size_report.pyor model construction that affects report output, run:Confirm the resulting diff contains only intentional generated updates.pixi run python generate_model_size_report.py
- Preserve the
MODEL_INSTANCEScoverage for models with multiple documented formulations or instances. Add labels, args, or kwargs for new cases instead of collapsing them to a single defaultbuild_model()call. - Model-size report generation should not require optional solver stacks. If a model normally solves a subproblem during construction, provide and test a solver-free construction path for the report generator.
- For generator changes, include focused tests for idempotent README updates,
per-model report generation, and any special-case instance or solver-free
paths added to
MODEL_INSTANCES.
- Model packages live under
gdplib/<model_name>/. - New model packages should include a
README.md, expose a callablebuild_model()fromgdplib/<model_name>/__init__.py, and be imported fromgdplib/__init__.py. build_model()should construct and return a Pyomo model. Avoid solving, network access, filesystem writes, or other side effects during model construction.- Package modules may use relative imports. Scripts intended to run as
__main__should use absolute imports, as described in the README. - Keep data files package-local and access them with paths relative to the module file.
- Preserve public import paths, model-builder names, and Pyomo model component
names such as
m.foounless the user explicitly asks for an API break. - When a change claims to simplify or reduce a GDP/Pyomo model, distinguish
between Pyomo modeling-layer objects and the solver-facing transformed model.
Report both when relevant: logical components such as
BooleanVarData, numeric variables, binary variables, constraints, disjunctions, disjuncts, and transformed model size after the intended GDP transformation. - For logical or GDP rewrites, test the supported transformation path in the
committed environment. Prefer direct
gdp.bigmsmoke tests when that is the intended path; do not add optional dependencies such assympysolely to test a transformation path that the project does not otherwise require. - When model semantics require exactly one disjunct, declare the
Disjunctionwithxor=True; do not rely only on a separateLogicalConstraintbecausegdp.hullrequires XOR disjunctions. - When a GDP choice is structurally impossible, do not leave an active disjunction with fixed indicator variables. Deactivate the impossible disjunction/disjuncts, fix the shared algebraic variables to enforce the same state, and test that no fixed choices remain active before benchmarking enumeration-based strategies.
- For algebraic GDP reformulations, add deterministic equivalence tests on
representative initialized data and smoke-test supported transformations such
as
gdp.bigmandgdp.hull. Keep this separate from solver-backed optimality evidence. - For pair-activation disjunctions, make inactive branches represent the true complement, such as "not both active," rather than an over-restrictive "both inactive" state. Test all representative binary combinations.
- When Pyomo/GDPopt FBBT or interval propagation fails, fix the narrow
modeling cause before changing solver configuration: derive finite bounds from
source constraints and parameters for missing-bound failures, and use explicit
products for small integer powers when negative-bounded variables break
PowExpression. Keep algebra equivalent, verify the affected transformation or GDPOpt path, and re-solve previously converged methods when practical to confirm bound tightening did not change the solution. - For ordered-location superstructures or activation-prefix rewrites, test the discrete semantics directly on a small representative instance. Enumerate the relevant binary activation/location choices and assert the intended valid combinations, including any derived feed, recycle, or terminal-location expressions. Component-type checks and transformation smoke tests are useful but are not enough to protect the modeling semantics.
- For scalable model changes, verify the documented/default instance and at least one larger instance when practical. If semantics should be unchanged, compare transformed model counts or generated algebraic representations where feasible.
- Runtime dependencies belong in
pyproject.toml,setup.py, andrequirements.txt. - Development-only tools belong in
requirements-dev.txtand the Pixi environment. - Keep optional external solver stacks and licensed solver bindings out of the default Pixi environment unless they are required for default imports and tests. Put GAMS, BARON, IPOPT, Gurobi, HiGHS, and similar tools in optional local environments, optional Pixi features, benchmark profiles, or documentation.
- Use
pixi run -e gurobi ...when local work needs directgurobipyor Pyomo's direct Gurobi solver interfaces. Pyomo's direct Gurobi writers do not support every nonlinear transformed GDP expression; for those cases, keep using the GAMS/Gurobi profile and record the limitation. - Packaging tests, Pixi runs, and benchmark runs can regenerate
gdplib/_version.pyor the editable package entry inpixi.lock. Do not commit that churn unless the task is explicitly about versioning, release metadata, or regenerating the Pixi lock. - When changing release workflow or Pixi support policy docs, update
tests/test_release_workflow.pyso README,publish.yml,pixi.toml, and AGENTS.md stay aligned. - PyPI publishing is intentionally release-driven. Keep publish workflows tied
to published GitHub Releases or another explicit maintainer release action,
not ordinary pushes or pull requests. Publishing should use PyPI Trusted
Publishing through the protected
pypienvironment unless maintainers choose and document another credential path. - Release workflow changes should build and validate real artifacts before any
publish step. Include
python -m build,python -m twine check dist/*, and a wheel install/import smoke test across the supported Python versions when practical. Keep these checks in CI or document why a local-only check is the best available evidence. setuptools_scmneeds intact git metadata to infer release versions. CI builds should check out full history, and local artifact checks should run from a real git checkout or set an explicit pretend version only when the purpose is isolated packaging validation. Do not rely on source copies without.gitmetadata as release evidence.- Pixi is the preferred Linux development environment, not a runtime requirement for PyPI users. A release should remain installable with standard pip tooling from the wheel or sdist, and package metadata should not depend on Pixi-only files.
- Treat packaging deprecation warnings from
setuptools,setuptools_scm, or PyPA tools as useful follow-up work. Keep warning cleanup in a focused metadata PR unless the warning causes the release build, artifact validation, or installation smoke test to fail.