This blog is part of the "Xpress Everywhere" series, showcasing how FICO® Xpress integrates with popular open-source optimization frameworks across different languages and modeling paradigms.
Read Part 1:Google OR-Tools
Read Part 2:Python Ecosystem (Pyomo, CVXPY, PuLP, PyOptInterface, Linopy)
JuMP (Julia for Mathematical Programming) is Julia's algebraic modeling framework for optimization. Variables, constraints, and objectives are declared using macros that mirror mathematical notation, and the model can be passed directly to FICO® Xpress Solver for solving optimization problems.
This post explores JuMP's capabilities through a unit commitment problem for power grid operations, covering how JuMP's syntax handles time-indexed decisions, startup logic, and operational constraints, all while leveraging Xpress's advanced solver features. We also show the same problem solved using XpressAPI.jl, FICO's own Julia package, which gives you direct access to the Xpress C API when maximum control is needed. Complete runnable examples are available in our GitHub repository.
Grid operators face a challenging optimization problem: which power plants should run, and at what output levels? Decisions must respect startup costs, minimum run times, ramping limits, and demand forecasts, all while solving in minutes for hundreds of generators across short- to medium-term horizons.
Given:
See the full mathematical formulation in the corresponding GitHub folder.
This problem combines:
The set_attribute function exposes any Xpress control from the Xpress Optimizer Reference Manual, such as MIP tolerances (MIPRELSTOP), presolve strategies (PRESOLVE), logging (OUTPUTLOG), threading (THREADS), and more.
The syntax power[g=1:n_gen, t=1:T] creates a two-dimensional array of variables indexed by generator and time. The bounds 0 <= ... <= generators.p_max are generator-specific, evaluated during variable creation. This compactness (declaring 120 power variables in three lines) showcases JuMP's expressiveness.
Each constraint array is named (such as demand_balance, spinning_reserve) and automatically indexed over the specified range. The syntax sum(power[g,t] for g in 1:n_gen) mirrors the mathematical summation over all generators.
This reads as: "IF start[g,t] = 1 (generator starts at period t), THEN it must be ON for the next min_up periods." When start[g,t] = 0, no constraint is imposed. The indicator form avoids big-M coefficient selection entirely and lets Xpress handle the internal representation.
The ramping constraint power[g,t] - power[g,t-1] <= generators.ramp_up expresses "power can't increase faster than the ramp rate" in the same form as a standard mathematical formulation.
The objective is a double summation over generators and time periods: one term for fuel cost per MWh and one for startup cost per startup event.
The value.() broadcast syntax extracts all variable values in one line; no loops needed. JuMP automatically queries the solver and populates arrays matching your variable structure. For the 5-generator, 24-hour example, this returns 5×24 matrices for on_solution and power_solution.
Xpress uses warm start solution information to initialize MIP heuristics and guide branching decisions, potentially leading to considerable speedups in rolling horizon scenarios compared to solving from scratch.
These metrics are essential for production systems: validate solution status and quality, monitor solve performance, and trigger fallback strategies when needed.
This transparency helps catch modeling errors early: verify that the right number of variables and constraints exist, inspect specific constraints, and validate variable domains.
No explicit XPRSdestroyprob or XPRSfree call is needed.
The columns are ordered as on variables first, then power, then start, matching the ordering defined by the helper functions above.
The 'G' row type means >=. Row types 'L' (<=) and 'E' (==) follow the same pattern.
The full low-level example is in unit_commitment_xpressapi.jl in the GitHub repository.
The key takeaway: start with JuMP. Its readability and correctness guarantees are worth the marginal overhead. Switch to the direct API only when profiling shows that model construction is the actual bottleneck. In Julia, wrap the model-building block in @elapsed and compare it to JuMP.solve_time(model) after solving. If build time dominates, the direct API is worth considering.
Problem Types
Download and install Julia from julialang.org/downloads
2. Install JuMP and XpressAPI.jl:
XpressAPI.jl is FICO's maintained package. Once a FICO Xpress installation is present and the XPRESSDIR environment variable is set, XpressAPI.jl automatically locates and loads the Xpress native libraries.
3. Get a FICO Xpress license:
Get a free FICO Xpress Community License.
The Community License supports all problem types for academic research, personal projects, and commercial evaluation; it is limited in the number of variables and constraints.
4. Explore the full example:
Clone the examples repository and run the unit commitment model:
xpress-3rdparty-interfaces
The jump/ folder contains unit_commitment_jump.jl (JuMP + XpressAPI.jl) and unit_commitment_xpressapi.jl (direct C API) with the complete examples.
5. Learn more:
JuMP Documentation: Comprehensive tutorials and API reference
XpressAPI.jl on GitHub: FICO's official Julia package
JuMP community forum: Active community for questions and discussions
Paired with FICO® Xpress via XpressAPI.jl, JuMP provides access to Xpress's full MIP solving capabilities: solver controls, warm starts, and solution quality metrics, all through a readable algebraic interface.
When you need to go beyond JuMP, XpressAPI.jl's direct C API provides the same Julia-friendly ergonomics with full control over model representation. The choice between the two is a matter of trade-offs: expressiveness and speed-of-development with JuMP, maximum control and fine-grained performance with the low-level Julia API.
For most optimization workflows, start with JuMP. At large scale, the direct API builds models 7–15× faster, but for typical problem sizes the abstraction cost is negligible. Switch to the direct API only when profiling shows model construction is the actual bottleneck.
Next in the series: We explore MiniZinc, a high-level constraint modeling language, and how it integrates with Xpress for combinatorial optimization problems.
──────────────────────────────────────────────────
Have questions or feedback? Join the discussion on the FICO Community.
Read Part 1:Google OR-Tools
Read Part 2:Python Ecosystem (Pyomo, CVXPY, PuLP, PyOptInterface, Linopy)
JuMP (Julia for Mathematical Programming) is Julia's algebraic modeling framework for optimization. Variables, constraints, and objectives are declared using macros that mirror mathematical notation, and the model can be passed directly to FICO® Xpress Solver for solving optimization problems.
This post explores JuMP's capabilities through a unit commitment problem for power grid operations, covering how JuMP's syntax handles time-indexed decisions, startup logic, and operational constraints, all while leveraging Xpress's advanced solver features. We also show the same problem solved using XpressAPI.jl, FICO's own Julia package, which gives you direct access to the Xpress C API when maximum control is needed. Complete runnable examples are available in our GitHub repository.
Grid operators face a challenging optimization problem: which power plants should run, and at what output levels? Decisions must respect startup costs, minimum run times, ramping limits, and demand forecasts, all while solving in minutes for hundreds of generators across short- to medium-term horizons.
The Problem: Unit Commitment in Power Systems
The unit commitment problem schedules electricity generators to meet demand at minimum cost while respecting technical constraints. It's a cornerstone of power grid operations, with billions of dollars in cost savings and reliability implications.Given:
- N generators with fuel costs, startup costs, and capacity limits
- 24-hour demand forecast that must be satisfied each hour
- Technical constraints: minimum up/down times, ramping rates, reserve and operational requirements (this is a simplified instance; real-world unit commitment also involves security, environmental, and legal constraints)
See the full mathematical formulation in the corresponding GitHub folder.
This problem combines:
- Binary decisions (on_g,t): Is generator g ON at time t?
- Continuous decisions (power_g,t): Power output at time t
- Startup indicators (start_g,t): Track when units turn ON
XpressAPI.jl: FICO's Julia Package
XpressAPI.jl is FICO's official Julia package for Xpress. It provides two distinct interfaces in one package, letting you choose the right level of abstraction for your use case:- JuMP/MOI interface (high level): A full MathOptInterface (MOI) implementation, so any JuMP model can use Xpress as its solver with a single line: Model(XpressAPI.Optimizer). You get all of JuMP's algebraic modeling syntax while the package transparently translates your model into Xpress data structures. This is a recommended entry point for most users.
- Low-level interface: thin Julia layer over the Xpress C functions (XPRScreateprob, XPRSaddrows, XPRSoptimize, ...) with Julia-friendly ergonomics: automatic resource management via do-blocks, lambda callbacks instead of Julia's @cfunction macro (which creates C-compatible function pointers), and output parameters returned as values instead of filled pointers. This interface is useful when you need full control over the problem representation, are integrating with existing C API workflows, or are pushing model build performance to its limits.
JuMP: Algebraic Modeling
JuMP provides a domain-specific language where optimization models mirror textbook notation. The @variable, @constraint, and @objective macros let you declare model components that look and feel like mathematical formulas. Type errors in variable indices, mismatched dimensions, and unsupported constraint types are caught at model-building time.Model Setup and Configuration
JuMP integrates with XpressAPI.jl through a solver-agnostic abstraction layer. Creating a model is straightforward:The set_attribute function exposes any Xpress control from the Xpress Optimizer Reference Manual, such as MIP tolerances (MIPRELSTOP), presolve strategies (PRESOLVE), logging (OUTPUTLOG), threading (THREADS), and more.
Time-Indexed Variables with Domains
JuMP's @variable macro supports multi-dimensional indexing and inline bounds:The syntax power[g=1:n_gen, t=1:T] creates a two-dimensional array of variables indexed by generator and time. The bounds 0 <= ... <= generators
Constraints That Look Like Mathematics
The @constraint macro mirrors mathematical notation. Constraint arrays with sum() and iterator syntax follow directly from the mathematical formulation:Each constraint array is named (such as demand_balance, spinning_reserve) and automatically indexed over the specified range. The syntax sum(power[g,t] for g in 1:n_gen) mirrors the mathematical summation over all generators.
Indicator Constraints
Unit commitment minimum up time is a natural fit for indicator constraints: a conditional formulation where a constraint is activated based on the value of a binary variable. In JuMP:This reads as: "IF start[g,t] = 1 (generator starts at period t), THEN it must be ON for the next min_up periods." When start[g,t] = 0, no constraint is imposed. The indicator form avoids big-M coefficient selection entirely and lets Xpress handle the internal representation.
Time-Coupling Constraints
The complexity of unit commitment lies in time-coupling: decisions in one period constrain future periods. These constraints can be stated as follows with JuMP:The ramping constraint power[g,t] - power[g,t-1] <= generators
Objective Function
The @objective macro supports both linear and quadratic objectives. The unit commitment objective is a sum of fuel and startup costs:The objective is a double summation over generators and time periods: one term for fuel cost per MWh and one for startup cost per startup event.
Solving and Extracting Results
Once the model is built, optimization is a single function call:The value.() broadcast syntax extracts all variable values in one line; no loops needed. JuMP automatically queries the solver and populates arrays matching your variable structure. For the 5-generator, 24-hour example, this returns 5×24 matrices for on_solution and power_solution.
Advanced Features: Xpress Through JuMP
JuMP exposes Xpress's advanced solver capabilities.Warm Starts for Rolling Horizon
Power grid operators can re-solve unit commitment as demand forecasts update. Warm starts (providing an initial feasible solution) accelerate these repeated solves:Xpress uses warm start solution information to initialize MIP heuristics and guide branching decisions, potentially leading to considerable speedups in rolling horizon scenarios compared to solving from scratch.
Solution Quality Metrics
After solving, JuMP provides access to solution quality information:These metrics are essential for production systems: validate solution status and quality, monitor solve performance, and trigger fallback strategies when needed.
Model Introspection
JuMP allows querying model structure before solving; useful for debugging and validation:This transparency helps catch modeling errors early: verify that the right number of variables and constraints exist, inspect specific constraints, and validate variable domains.
Going Deeper: XpressAPI.jl's Direct C API
For applications where model construction time matters, XpressAPI.jl exposes the full Xpress C API directly. The same unit commitment problem built above in JuMP can be built using low-level calls that map one-to-one with the Xpress C documentation.Resource Management with Do-Blocks
The entry point is XPRScreateprob, which follows Julia's do-block idiom for automatic cleanup:No explicit XPRSdestroyprob or XPRSfree call is needed.
Adding Variables
Variables can be added in one call via XPRSaddcols, using flat vectors indexed by column position. Helper functions map each (generator, time) pair to a column index:The columns are ordered as on variables first, then power, then start, matching the ordering defined by the helper functions above.
Adding Constraints
Constraints are added via XPRSaddrows in sparse row format. The demand balance constraint adds one row per time period:The 'G' row type means >=. Row types 'L' (<=) and 'E' (==) follow the same pattern.
Warm Start and Solve
Providing an initial solution and solving follow the same concept as JuMP, using explicit function calls:The full low-level example is in unit_commitment_xpressapi.jl in the GitHub repository.
Model building time: JuMP vs. Direct API
For large-scale unit commitment instances (hundreds of generators over week-long horizons), the direct API builds the model 7–15× faster than the JuMP layer, as problem size grows into the hundreds of thousands of variables and constraints.The key takeaway: start with JuMP. Its readability and correctness guarantees are worth the marginal overhead. Switch to the direct API only when profiling shows that model construction is the actual bottleneck. In Julia, wrap the model-building block in @elapsed and compare it to JuMP.solve_time(model) after solving. If build time dominates, the direct API is worth considering.
Xpress Features Supported Through JuMP
JuMP exposes a comprehensive set of Xpress capabilities:Problem Types
- LP, QP, MIP: simplex, barrier, branch-and-cut
- SOCP: via conic constraints
- Linear and quadratic: ≤, ≥, = and convex quadratic
- SOS Type 1 & 2: at most one / two adjacent non-zero
- Indicator: binary activates a linear constraint
- Time, work, gap, threads: TIMELIMIT, WORKLIMIT, MIPRELSTOP, THREADS, HEUREMPHASIS, ...
- Warm starts: set_start_value()
When JuMP + Xpress Excels
- Readable, maintainable models: JuMP's mathematical notation eliminates the gap between model formulation on paper and code on screen. Models are self-documenting and can be reviewed by team members without deep solver knowledge.
- Research and teaching: Models are self-documenting and closely resemble the mathematical exposition in papers and textbooks. A JuMP model can serve as a precise specification that non-programmers can verify against the business problem, bridging the gap between domain experts and implementation.
- Low performance cost at typical scales: JuMP's abstraction overhead is negligible for most real-world problems. The solve itself is identical to direct API calls since both invoke the same Xpress engine. Build time overhead becomes noticeable only at very large scales (hundreds of generators, week-long horizons), where the direct API offers a faster build at the cost of significantly more verbose code.
- Rapid iteration and exploration: Adding or modifying constraints, switching between LP relaxation and MIP, or restructuring the objective is straightforward when the code mirrors the mathematical formulation.
Getting Started
1. Install Julia:Download and install Julia from julialang.org/downloads
2. Install JuMP and XpressAPI.jl:
XpressAPI.jl is FICO's maintained package. Once a FICO Xpress installation is present and the XPRESSDIR environment variable is set, XpressAPI.jl automatically locates and loads the Xpress native libraries.
3. Get a FICO Xpress license:
Get a free FICO Xpress Community License.
The Community License supports all problem types for academic research, personal projects, and commercial evaluation; it is limited in the number of variables and constraints.
4. Explore the full example:
Clone the examples repository and run the unit commitment model:
xpress-3rdparty-interfaces
The jump/ folder contains unit_commitment_jump.jl (JuMP + XpressAPI.jl) and unit_commitment_xpressapi.jl (direct C API) with the complete examples.
5. Learn more:
JuMP Documentation: Comprehensive tutorials and API reference
XpressAPI.jl on GitHub: FICO's official Julia package
JuMP community forum: Active community for questions and discussions
Conclusion
JuMP brings algebraic modeling to Julia. Variables, constraints, and objectives mirror textbook notation; models are expressed in the same form as their mathematical definitions, not as matrix indices. The @variable and @constraint macros catch errors at build time, and multi-indexed arrays make temporal and multi-dimensional problems natural.Paired with FICO® Xpress via XpressAPI.jl, JuMP provides access to Xpress's full MIP solving capabilities: solver controls, warm starts, and solution quality metrics, all through a readable algebraic interface.
When you need to go beyond JuMP, XpressAPI.jl's direct C API provides the same Julia-friendly ergonomics with full control over model representation. The choice between the two is a matter of trade-offs: expressiveness and speed-of-development with JuMP, maximum control and fine-grained performance with the low-level Julia API.
For most optimization workflows, start with JuMP. At large scale, the direct API builds models 7–15× faster, but for typical problem sizes the abstraction cost is negligible. Switch to the direct API only when profiling shows model construction is the actual bottleneck.
Next in the series: We explore MiniZinc, a high-level constraint modeling language, and how it integrates with Xpress for combinatorial optimization problems.
──────────────────────────────────────────────────
Have questions or feedback? Join the discussion on the FICO Community.
Updated 5 months ago
Version 3.0FICO
Community Manager
Joined July 21, 2025
FICO® Xpress Optimization Blog
Follow this blog board to get notified when there's new activity