Skip to main content

Convert GAMS NLP models to MCP via KKT conditions

Project description

NLP2MCP: Convert GAMS NLP to MCP via KKT Conditions

CI Lint PyPI version Python Support

A Python tool that transforms Nonlinear Programming (NLP) models written in GAMS into equivalent Mixed Complementarity Problems (MCP) by generating the Karush-Kuhn-Tucker (KKT) conditions.

Overview

This project automates the process of converting a GAMS NLP model into its KKT-based MCP formulation, which is useful for:

  • Mathematical research: Analyzing stationarity conditions of nonlinear programs
  • Solver development: Testing MCP solvers on problems derived from NLPs
  • Educational purposes: Understanding the relationship between NLP and MCP formulations
  • Advanced modeling: Working with equilibrium problems and complementarity conditions

Background

The standard recipe for NLP → MCP transformation is:

  1. Start with a nonlinear program (NLP)
  2. Write down its KKT (Karush-Kuhn-Tucker) conditions
  3. Encode those KKT conditions as a Mixed Complementarity Problem (MCP):
    • Equations for stationarity and equality constraints
    • Complementarity pairs for inequalities and bounds

For more details, see docs/concepts/IDEA.md and docs/concepts/NLP2MCP_HIGH_LEVEL.md.

Features

Epic 1 (Sprints 1-5) is complete, delivering core NLP to MCP transformation capabilities. For detailed sprint summaries, see docs/planning/EPIC_1/SUMMARY.md.

Sprint 6 Progress (Epic 2 - Sprint 6: Convexity Heuristics, Bug Fixes, GAMSLib, UX):

  • Day 0: Pre-Sprint Research & Setup
  • Day 1: Nested Min/Max Research
  • Day 2: Nested Min/Max Implementation
  • Day 3: Convexity Heuristics - Core Patterns
  • Day 4: Convexity Heuristics - CLI Integration
  • Day 5: GAMSLib Integration - Model Ingestion
  • Day 6: GAMSLib Integration - Conversion Dashboard
  • Day 7: UX Improvements - Error Message Integration
  • Day 8: UX Improvements - Documentation & Polish
  • Day 9: Testing & Quality Assurance
  • Day 10: Release Preparation & Sprint Review

For the detailed Sprint 6 plan, see docs/planning/EPIC_2/SPRINT_6/PLAN.md.

Sprint 7 Progress (Epic 2 - Sprint 7: Parser Enhancements & GAMSLib Expansion):

  • Day 0: Pre-Sprint Setup & Kickoff
  • Day 1: Preprocessor Directives (Part 1)
  • Day 2: Preprocessor Directives (Part 2) + Set Range Syntax (Part 1)
  • Day 3: Set Range Syntax (Part 2)
  • Day 4: Parser Integration & Testing + Quick Wins
  • Day 5: GAMSLib Retest & Checkpoint 1 (Fixtures created, parse rate 20% ModelIR / 50% grammar)
  • Day 6: Test Performance (Part 1) - pytest-xdist (1277 tests pass in parallel, ~2min runtime)
  • Day 7: Test Performance (Part 2) & Checkpoint 2 (Fast: 29s, Full: 111s, 8 slow tests marked)
  • Day 8: Convexity UX + Multi-Dim Fixtures
  • Day 9: CI Automation + Statement Fixtures & Checkpoint 3 (Regression CI + 34 total fixtures)
  • Day 10: Sprint Review, Release & Checkpoint 4

For the detailed Sprint 7 plan, see docs/planning/EPIC_2/SPRINT_7/PLAN.md.

Installation

Requirements

  • Python 3.11 or higher
  • pip 21.3 or higher

Quick Start

Install from PyPI:

pip install nlp2mcp

Verify installation:

nlp2mcp --help

From Source (Development)

For contributing or development:

# Clone the repository
git clone https://github.com/jeffreyhorn/nlp2mcp.git
cd nlp2mcp

# Create a virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install with development dependencies
make install-dev

# Or manually:
pip install -e .
pip install -r requirements.txt

Beta/Pre-release Versions

To test beta releases:

# Install specific version
pip install nlp2mcp==0.5.0b0

# Or install from TestPyPI
pip install --index-url https://test.pypi.org/simple/ \
    --extra-index-url https://pypi.org/simple/ \
    nlp2mcp

# Or install directly from GitHub
pip install git+https://github.com/jeffreyhorn/nlp2mcp.git

Usage

Command Line Interface

Note: the package exposes a console script nlp2mcp (defined in pyproject.toml as [project.scripts] nlp2mcp = "src.cli:main"). After installing the package (for example with pip install -e . or pip install nlp2mcp), the nlp2mcp command will be available on your PATH and will invoke src.cli:main.

If you prefer not to install the package, you can run the CLI directly from the repository with:

python -m src.cli examples/simple_nlp.gms -o output_mcp.gms
# Convert NLP to MCP
nlp2mcp input.gms -o output_mcp.gms

# Print to stdout
nlp2mcp input.gms

# Verbose output (show pipeline stages)
nlp2mcp input.gms -o output.gms -v

# Very verbose (show detailed statistics)
nlp2mcp input.gms -o output.gms -vv

# Quiet mode (errors only)
nlp2mcp input.gms -o output.gms --quiet

# Show model statistics
nlp2mcp input.gms -o output.gms --stats

# Export Jacobian structure for analysis
nlp2mcp input.gms -o output.gms --dump-jacobian jacobian.mtx

# Apply Curtis-Reid scaling for ill-conditioned systems
nlp2mcp input.gms -o output.gms --scale auto

# Enable smooth abs() approximation
nlp2mcp input.gms -o output.gms --smooth-abs

# Customize model name
nlp2mcp input.gms -o output.gms --model-name my_mcp_model

# Disable explanatory comments
nlp2mcp input.gms -o output.gms --no-comments

# Show excluded duplicate bounds
nlp2mcp input.gms -o output.gms --show-excluded

CLI Options:

  • -o, --output FILE: Output file path (default: stdout)
  • -v, --verbose: Increase verbosity (stackable: -v, -vv, -vvv)
  • -q, --quiet: Suppress non-error output
  • --model-name NAME: Custom GAMS model name (default: mcp_model)
  • --show-excluded / --no-show-excluded: Show duplicate bounds excluded (default: no)
  • --no-comments: Disable explanatory comments in output
  • --stats: Print model statistics (equations, variables, nonzeros)
  • --dump-jacobian FILE: Export Jacobian structure to Matrix Market format
  • --scale {none,auto,byvar}: Apply scaling (default: none)
  • --simplification {none,basic,advanced}: Expression simplification mode (default: advanced)
  • --smooth-abs: Enable smooth abs() approximation via sqrt(x²+ε)
  • --smooth-abs-epsilon FLOAT: Epsilon for abs smoothing (default: 1e-6)
  • --help: Show help message

Expression Simplification

nlp2mcp automatically simplifies derivative expressions to produce more compact and efficient MCP formulations. The simplification mode can be controlled via the --simplification flag or configuration file.

Simplification Modes

Advanced (default) - --simplification advanced

  • Applies all basic simplifications plus algebraic term collection

Additive term collection:

  • Constant collection: 1 + x + 1 → x + 2
  • Like-term collection: x + y + x + y → 2*x + 2*y
  • Coefficient collection: 2*x + 3*x → 5*x
  • Term cancellation: x - x → 0, x + y - x → y
  • Complex bases: x*y + 2*x*y → 3*x*y

Multiplicative term collection:

  • Variable collection: x * x → x^2, x * x * x → x^3
  • Power multiplication: x^2 * x^3 → x^5
  • Mixed multiplication: x^2 * x → x^3, x * x^2 → x^3

Other algebraic rules:

  • Multiplicative cancellation: 2*x / 2 → x, 2*x / (1+1) → x
  • Power division: x^5 / x^2 → x^3, x / x^2 → 1/x
  • Nested powers: (x^2)^3 → x^6

Recommended for most use cases - produces cleanest output

Basic - --simplification basic

  • Applies only fundamental simplification rules:
    • Constant folding: 2 + 3 → 5, 4 * 5 → 20
    • Zero elimination: x + 0 → x, 0 * x → 0
    • Identity elimination: x * 1 → x, x / 1 → x, x^1 → x
    • Algebraic identities: x - x → 0, x / x → 1
  • Use when you want minimal transformation of expressions

None - --simplification none

  • No simplification applied
  • Derivative expressions remain in raw differentiated form
  • Useful for debugging or understanding the differentiation process
  • May produce very large expressions

Examples

# Default: advanced simplification
nlp2mcp model.gms -o output.gms

# Explicitly use advanced
nlp2mcp model.gms -o output.gms --simplification advanced

# Use basic simplification only
nlp2mcp model.gms -o output.gms --simplification basic

# Disable simplification
nlp2mcp model.gms -o output.gms --simplification none

Configuration File

You can set the default simplification mode in pyproject.toml:

[tool.nlp2mcp]
simplification = "advanced"  # or "basic" or "none"
scale = "none"
smooth_abs = false

When to Use Each Mode

  • Advanced (default): Best for production use - produces cleanest, most readable output
  • Basic: When you need predictable transformations without aggressive optimization
  • None: For debugging, education, or when you need to see raw derivative expressions

Complete Example

Input (examples/scalar_nlp.gms):

Variables x, obj;
Scalars a /2.0/;
Equations objective, stationarity;

objective.. obj =E= x;
stationarity.. x + a =E= 0;

Model mymodel /all/;
Solve mymodel using NLP minimizing obj;

Run nlp2mcp:

nlp2mcp examples/scalar_nlp.gms -o output_mcp.gms

Output (output_mcp.gms):

* Generated by nlp2mcp
* KKT System with stationarity, complementarity, and multipliers

Scalars
    a /2.0/
;

Variables
    x
    obj
    nu_objective
    nu_stationarity
;

Equations
    stat_x
    objective
    stationarity
;

stat_x.. 1 + nu_stationarity =E= 0;
objective.. obj =E= x;
stationarity.. x + a =E= 0;

Model mcp_model /
    stat_x.x,
    objective.obj,
    stationarity.nu_stationarity
/;

Solve mcp_model using MCP;

Python API

After an editable install (pip install -e .) the package imports use the package name. Example usage:

from nlp2mcp.ir.parser import parse_model_file
from nlp2mcp.ir.normalize import normalize_model
from nlp2mcp.ad.gradient import compute_objective_gradient
from nlp2mcp.ad.constraint_jacobian import compute_constraint_jacobian
from nlp2mcp.kkt.assemble import assemble_kkt_system
from nlp2mcp.emit.emit_gams import emit_gams_mcp

# Full pipeline
model = parse_model_file("examples/simple_nlp.gms")
normalize_model(model)
gradient = compute_objective_gradient(model)
J_eq, J_ineq = compute_constraint_jacobian(model)
kkt = assemble_kkt_system(model, gradient, J_eq, J_ineq)
gams_code = emit_gams_mcp(kkt, model_name="mcp_model", add_comments=True)

print(gams_code)

Note: if you prefer running from the repository without installing, either set PYTHONPATH=., or run modules directly (for example python -m src.cli ...), but the recommended workflow for development is an editable install so imports use nlp2mcp.*.

Project Structure

nlp2mcp/
├── src/
│   ├── ad/           # Symbolic differentiation engine
│   │   ├── api.py              # High-level API
│   │   ├── differentiate.py    # Core differentiation rules
│   │   ├── simplify.py         # Expression simplification
│   │   ├── evaluate.py         # AST evaluation
│   │   ├── gradient.py         # Gradient computation
│   │   ├── jacobian.py         # Jacobian computation
│   │   ├── mapping.py          # Index mapping utilities
│   │   └── validation.py       # Finite-difference validation
│   ├── emit/         # Code generation for GAMS MCP (planned)
│   ├── gams/         # GAMS grammar and parsing utilities
│   ├── ir/           # Intermediate representation
│   │   ├── ast.py              # Expression AST nodes
│   │   ├── model_ir.py         # Model IR data structures
│   │   ├── normalize.py        # Constraint normalization
│   │   ├── parser.py           # GAMS parser
│   │   └── symbols.py          # Symbol table definitions
│   ├── kkt/          # KKT system assembly (planned)
│   └── utils/        # Utility functions
├── tests/
│   ├── ad/           # Differentiation tests
│   ├── gams/         # Parser tests
│   └── ir/           # IR and normalization tests
├── examples/         # Example GAMS models
├── docs/             # Additional documentation
│   ├── ad/                   # Automatic differentiation docs
│   ├── architecture/         # System architecture
│   ├── emit/                 # GAMS emission docs
│   ├── kkt/                  # KKT assembly docs
│   └── planning/             # Sprint plans and retrospectives
├── pyproject.toml    # Project configuration
├── Makefile          # Development commands
└── README.md         # This file

Development

Available Make Commands

make help         # Show all available commands
make install      # Install the package
make install-dev  # Install with dev dependencies
make lint         # Run linters (ruff, mypy)
make format       # Format code (black, ruff)
make test         # Run tests
make clean        # Remove build artifacts

Running Tests

The test suite is organized into four layers for fast feedback.

📊 View Test Pyramid Visualization - See test coverage breakdown by module and type.

# Run fast unit tests only (~10 seconds)
./scripts/test_fast.sh
# Or: pytest tests/unit/ -v

# Run unit + integration tests (~30 seconds)
./scripts/test_integration.sh
# Or: pytest tests/unit/ tests/integration/ -v

# Run complete test suite (~60 seconds)
./scripts/test_all.sh
# Or: pytest tests/ -v

# Run specific test category
pytest -m unit          # Only unit tests
pytest -m integration   # Only integration tests
pytest -m e2e          # Only end-to-end tests
pytest -m validation   # Only validation tests

# Run specific test file
pytest tests/unit/ad/test_arithmetic.py -v

# Run with coverage
pytest --cov=src tests/

# Run tests in parallel (faster, ~2 minutes for full suite)
pytest -n 4             # Use 4 workers
pytest -n auto          # Auto-detect CPU count

Parallel Testing: The test suite supports parallel execution using pytest-xdist. Running with -n 4 reduces test time from ~3-4 minutes to ~2 minutes. All tests are isolated and safe for parallel execution.

Test Organization

The test suite is split into unit, integration, e2e, and validation layers. You can run the different subsets with the scripts in ./scripts/ or via pytest directly. Below are the counts collected locally on Nov 5, 2025 (run in this repository with python3 -m pytest --collect-only):

  • Total collected tests: 1281
  • Marker breakdown (may overlap if tests carry multiple markers):
    • unit: 434
    • integration: 223
    • e2e: 45
    • validation: 66

Note: marker-based counts can overlap and the total may include tests without markers or additional collected items (fixtures, doctests, etc.). To reproduce these numbers locally run:

# Total collected tests
python3 -m pytest --collect-only -q | wc -l

# Per-marker counts
python3 -m pytest -m unit --collect-only -q | wc -l
python3 -m pytest -m integration --collect-only -q | wc -l
python3 -m pytest -m e2e --collect-only -q | wc -l
python3 -m pytest -m validation --collect-only -q | wc -l

Typical layout:

tests/
├── unit/
├── integration/
├── e2e/
└── validation/

Test pyramid guidance: prefer fast unit tests during development, run integration/e2e for cross-module confidence, and run the full validation/validation suite before releases.

Code Style

This project uses:

  • Black for code formatting (line length: 100)
  • Ruff for linting and import sorting
  • MyPy for type checking

Format your code before committing:

make format
make lint

Examples

The examples/ directory contains sample GAMS NLP models:

  • simple_nlp.gms - Basic indexed NLP with objective and constraints
  • scalar_nlp.gms - Simple scalar optimization problem
  • indexed_balance.gms - Model with indexed balance equations
  • bounds_nlp.gms - Demonstrates variable bounds handling
  • nonlinear_mix.gms - Mixed nonlinear functions

Supported GAMS Subset

Declarations

  • Sets with explicit members
  • Aliases
  • Parameters (scalar and indexed)
  • Scalars
  • Variables (scalar and indexed)
  • Equations (scalar and indexed)
  • Table data blocks

Preprocessing

  • $include directive (nested, relative paths)

Comments

  • ✅ GAMS inline comments (* comment)
  • ✅ C-style line comments (// comment)
  • ✅ Block comments ($ontext ... $offtext)

Note: Input file comments are stripped during parsing and do not appear in the output. However, the emitter can add explanatory comments to the output (controlled by --no-comments flag).

Expressions

  • ✅ Arithmetic: +, -, *, /, ^
  • ✅ Functions: exp, log, sqrt, sin, cos, tan
  • ✅ Aggregation: sum(i, expr)
  • ✅ Comparisons: =, <>, <, >, <=, >=
  • ✅ Logic: and, or
  • min() and max() (reformulated to complementarity)
  • abs() (smooth approximation with --smooth-abs)

Equations

  • ✅ Relations: =e= (equality), =l= (≤), =g= (≥)
  • ✅ Variable bounds: .lo, .up, .fx

Model

  • Model declaration with equation lists or /all/
  • Solve statement with using NLP and objective

Advanced Features

  • Scaling: Curtis-Reid and byvar scaling (--scale auto|byvar)
  • Diagnostics: Model statistics (--stats), Jacobian export (--dump-jacobian)
  • Configuration: pyproject.toml support for default options
  • Logging: Structured logging with verbosity control (--verbose, --quiet)

Not Yet Supported

  • ❌ Control flow (Loop, If, While)
  • ❌ Other $ directives ($if, $set, etc.)
  • ❌ External/user-defined functions
  • ❌ Other non-differentiable functions (floor, ceil, sign, etc.)

Documentation

Concepts & Planning

Technical Documentation

System Architecture:

Automatic Differentiation:

KKT Assembly & Code Generation:

Contributing

Please read CONTRIBUTING.md before contributing!

This project is in active development (Sprint 5 in progress - hardening, packaging, and documentation). Contributions are welcome!

Quick Start for Contributors

  1. Read guidelines: CONTRIBUTING.md and docs/development/AGENTS.md
  2. Setup environment:
    python3.12 -m venv .venv
    source .venv/bin/activate
    make install-dev
    
  3. Create feature branch: git checkout -b feature/amazing-feature
  4. Make changes: Follow code style in CONTRIBUTING.md
  5. Quality checks:
    make format   # Auto-format code
    make lint     # Type checking and linting
    make test     # All tests must pass (602+ tests)
    
  6. Submit PR: Push branch and create Pull Request on GitHub

Requirements

  • Python 3.12+ with modern type hints
  • All tests passing
  • Code formatted with Black + Ruff
  • Type checked with mypy

See CONTRIBUTING.md for detailed guidelines.

License

MIT License - See LICENSE file for details

Acknowledgments

  • Based on the mathematical framework of KKT conditions for nonlinear optimization
  • Uses Lark for parsing GAMS syntax
  • Inspired by GAMS/PATH and other MCP solvers

Roadmap

  • v0.1.0 (Sprint 1): ✅ Parser and IR - COMPLETE
  • v0.2.0 (Sprint 2): ✅ Symbolic differentiation - COMPLETE
  • v0.3.0 (Sprint 3): ✅ KKT synthesis and MCP code generation - COMPLETE
  • v0.3.1 (Post Sprint 3): ✅ Issue #47 fix (indexed equations) - COMPLETE
  • v0.4.0 (Sprint 4): ✅ Extended features and robustness - COMPLETE
  • v1.0.0 (Sprint 5): 🔄 Production-ready with hardening, packaging, and comprehensive documentation - IN PROGRESS

Contact

For questions, issues, or suggestions, please open an issue on GitHub.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nlp2mcp-0.7.0.tar.gz (143.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nlp2mcp-0.7.0-py3-none-any.whl (165.5 kB view details)

Uploaded Python 3

File details

Details for the file nlp2mcp-0.7.0.tar.gz.

File metadata

  • Download URL: nlp2mcp-0.7.0.tar.gz
  • Upload date:
  • Size: 143.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for nlp2mcp-0.7.0.tar.gz
Algorithm Hash digest
SHA256 f07207969943775c018f3f8df1a86eca9ecca18e4afc7c8c49f82c838bd0ff30
MD5 944a120b0fb9374e4057b0fd1cbbaf70
BLAKE2b-256 688300990f5b2c60350dad9aec70ce7e61b937cb37636b2be7db1dfe7d113536

See more details on using hashes here.

File details

Details for the file nlp2mcp-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: nlp2mcp-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 165.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for nlp2mcp-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 64a2fc6baac16d5e60edcb80f16b43b50759d925b2d440c5cc05351468cbd608
MD5 6f46cb013f154944986184d6b4fd3c1e
BLAKE2b-256 169a9f006ac92aad33da0e0497cffb5844d127f997bf0f193fc07bf756a51cad

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page