MPAX: Mathematical Programming in JAX

repository·main·Indexed 41 days ago

https://github.com/mit-lu-lab/mpax

A hardware-accelerated, differentiable, and batchable solver for Linear Programming (LP) and Quadratic Programming (QP) built on JAX. Version 0.2.4 provides solver implementations r2HPDHG and raPDHG, and utilities for constructing optimization problems via create_lp and create_qp, including support for Gurobi model transformation and JAX sparse matrix formats like BCOO and BCSR.

Tokens
18.4K
Snippets
65
Records
72
Agent score
51%

What's inside MPAX

  1. Differentiate through optimization problems

    main

    MPAX is differentiable via unrolled differentiation. This allows you to integrate mathematical programming directly into the backpropagation process of neural networks. You can use jax.custom_vjp or jax.custom_jvp to define custom forward and backward passes for loss functions that depend on optimization results.

    import jax
    import jax.numpy as jnp
    
    @jax.custom_vjp
    def pso_fun(pred_cost, true_cost, true_sol, true_obj):
        sol, obj = batch_optimize(2*pred_cost - true_cost)
        loss = -obj + 2 * jnp.sum(pred_cost * true_sol, axis=1) - true_obj
        loss = jnp.mean(loss)
        return loss, sol
    
    def spo_fwd(pred_cost, true_cost, true_sol, true_obj):
        loss, sol = pso_fun(pred_cost, true_cost, true_sol, true_obj)
        return loss, (sol, true_sol)
    
    def spo_bwd(res, g):
        sol, true_sol = res
        grad = 2 * (true_sol - sol)
        # No gradients needed for true_cost, true_sol, or true_obj
        return grad * g, None, None, None
    
    pso_fun.defvjp(spo_fwd, spo_bwd)
  2. Install MPAX

    main

    You can install the latest released version of MPAX from PyPI or the latest development version from GitHub.

    PyPI (Latest Release):

    pip install mpax

    GitHub (Development Version):

    pip install git+https://github.com/MIT-Lu-Lab/mpax.git
    pip install mpax
  3. Solve a single LP or QP problem

    main

    MPAX supports Linear Programming (LP) and Quadratic Programming (QP). You can define problems using either sparse or dense matrix formats for constraints. By default, create_lp and create_qp use sparse matrix formats (use_sparse_matrix=True).

    To solve a problem, create the problem object using create_lp or create_qp, instantiate a solver (raPDHG or r2HPDHG), and call .optimize(problem).

    from mpax import create_lp, create_qp, raPDHG, r2HPDHG
    
    # Create LP using sparse matrix format (default)
    lp = create_lp(c, A, b, G, h, l, u) # use_sparse_matrix=True by default
    
    # Create LP using dense matrix format
    lp = create_lp(c, A, b, G, h, l, u, use_sparse_matrix=False)
    
    solver = r2HPDHG(eps_abs=1e-4, eps_rel=1e-4, verbose=True)
    result = solver.optimize(lp)
    
    # Or create QP
    qp = create_qp(Q, c, A, b, G, h, l, u)
    qp = create_qp(Q, c, A, b, G, h, l, u, use_sparse_matrix=False)
    solver = raPDHG(eps_abs=1e-4, eps_rel=1e-4, verbose=True)
    result = solver.optimize(qp)
  4. Distribute computations with device parallelism

    main

    MPAX integrates with JAX's sharding capabilities to distribute computations across multiple devices (e.g., multiple GPUs). You can achieve this by sharding your constraint matrices and using jax.jit on the solver's optimize method.

    import jax
    from mpax import create_lp
    
    # Data sharding setup
    mesh = jax.make_mesh((2,), ('x',))
    sharding = jax.sharding.NamedSharding(mesh, P('x',))
    
    A_sharded = jax.device_put(A, sharding)
    lp_sharded = create_lp(c, A_sharded, b, G, h, l, u)
    
    solver = r2HPDHG(eps_abs=1e-4, eps_rel=1e-4, verbose=True)
    jit_optimize = jax.jit(solver.optimize)
    result = jit_optimize(lp_sharded)
  5. How Pock-Chambolle rescaling works

    main

    The pock_chambolle_rescaling function implements diagonal preconditioning as proposed by Pock and Chambolle (2011).

    Given an exponent $\alpha \in [0, 2]$, it rescales the problem such that the operator norm of the rescaled constraint matrix is $\le 1$. This is achieved by:

    1. Dividing each column of the constraint matrix by $\sqrt{\sum |e|^{2-\alpha}}$ (where $e$ are elements in the column).
    2. Dividing each row of the constraint matrix by $\sqrt{\sum |e|^{\alpha}}$ (where $e$ are elements in the row).

    This rescaling is particularly beneficial for the convergence of Primal-Dual Hybrid Gradient (PDHG) algorithms.

  6. How Ruiz rescaling works

    main

    The ruiz_rescaling function implements a modified Ruiz algorithm to rescale the augmented matrix $M = [Q, A'; A, 0]$, where $Q$ is the objective_matrix and $A$ is the constraint_matrix.

    It iteratively updates scaling vectors for both constraints and variables to improve conditioning. The user can specify the norm $p$ to use (either $2$ or $\infty$).

    Returns a tuple of (cum_constraint_rescaling, cum_variable_rescaling) such that the original problem can be recovered using unscale_problem.

  7. Configure solver options

    main

    MPAX solvers accept several parameters to control execution behavior, convergence, and precision.

    General Options

    ParameterTypeDefaultDescription
    verboseboolFalseEnables detailed logging of the solver's progress
    debugboolFalseActivates additional debugging information
    display_frequencyint10Frequency (in every termination check) for displaying solver statistics
    jitboolTrueEnables JIT (Just-In-Time) compilation for faster execution
    unrollboolFalseUnrolls iteration loops
    warm_startboolFalseWhether to perform warm starting
    feasibility_polishingboolFalseWhether to perform feasibility polishing

    Termination Parameters

    ParameterTypeDefaultDescription
    eps_absfloat1e-4Absolute tolerance for convergence
    eps_relfloat1e-4Relative tolerance for convergence
    eps_primal_infeasiblefloat1e-8Tolerance for detecting primal infeasibility
    eps_dual_infeasiblefloat1e-8Tolerance for detecting dual infeasibility
    eps_feas_polishfloat1e-6Tolerance for feasibility polishing
    iteration_limitintmax_intMaximum number of iterations allowed

    Precision and Determinism

    Enable Double Precision (64-bit): By default, MPAX uses single-precision (32-bit). To enable 64-bit, run this at the start of your script:

    jax.config.update("jax_enable_x64", True)

    Enable Determinism: To ensure deterministic results on GPUs, set the following environment variable:

    os.environ["XLA_FLAGS"] = "--xla_gpu_deterministic_ops=true"

    Warning: Do not enable --xla_gpu_deterministic_ops=true when using batch solving, as it significantly degrades performance.

  8. Batch solve multiple LP problems

    main

    You can solve multiple problem instances of the same shape simultaneously by using jax.vmap. This is highly efficient for large-scale applications where many similar optimization problems need to be solved in parallel.

    import jax.numpy as jnp
    import jax
    from mpax import create_lp, r2HPDHG
    
    def single_optimize(c_vector):
        lp = create_lp(c_vector, A, b, G, h, l, u)
        solver = r2HPDHG(eps_abs=1e-4, eps_rel=1e-4, verbose=True)
        result = solver.optimize(lp)
        obj = jnp.dot(c_vector, result.primal_solution)
        return result.primal_solution, obj
    
    batch_size = 100
    batch_c = jnp.tile(c, (batch_size, 1))
    batch_optimize = jax.vmap(single_optimize)
    
    result = batch_optimize(batch_c)
  9. Configure restart schemes with RestartParameters

    main

    The RestartParameters NamedTuple controls how the algorithm performs restarts to improve convergence.

    Key parameters:

    • restart_scheme: An instance of RestartScheme (NO_RESTARTS, FIXED_FREQUENCY, or ADAPTIVE_KKT).
    • restart_to_current_metric: An instance of RestartToCurrentMetric (NO_RESTART_TO_CURRENT or KKT_GREEDY) to decide between restarting to the average or the current iterate.
    • restart_frequency_if_fixed: Frequency of restarts if using FIXED_FREQUENCY.
    • artificial_restart_threshold: Fraction of iterations without a restart to trigger an artificial restart.
    • sufficient_reduction_for_restart: Threshold improvement to trigger a restart.
    • necessary_reduction_for_restart: Necessary threshold improvement to trigger a restart.
    from mpax.utils import RestartParameters, RestartScheme, RestartToCurrentMetric
    
    params = RestartParameters(
        restart_scheme=RestartScheme.ADAPTIVE_KKT,
        restart_to_current_metric=RestartToCurrentMetric.KKT_GREEDY,
        restart_frequency_if_fixed=100,
        artificial_restart_threshold=0.5,
        sufficient_reduction_for_restart=0.1,
        necessary_reduction_for_restart=0.01,
        primal_weight_update_smoothing=0.1
    )
  10. Configure solver tolerances with TerminationCriteria

    main

    The TerminationCriteria NamedTuple defines the tolerances used to determine optimality, primal feasibility, and dual feasibility.

    When termination_status == OPTIMAL, the following conditions are met:

    1. Duality Gap: | primal_objective - dual_objective | <= eps_abs + eps_rel * ( | primal_objective | + | dual_objective | )
    2. Primal Feasibility: norm(primal_residual, p) <= eps_abs + eps_rel * norm(right_hand_side, p)
    3. Dual Feasibility: norm(dual_residual, p) <= eps_abs + eps_rel * norm(objective_vector, p)

    Attributes:

    • eps_abs: Absolute tolerance.
    • eps_rel: Relative tolerance.
    • eps_primal_infeasible: Tolerance for identifying approximate dual rays (certificate of primal infeasibility).
    • eps_dual_infeasible: Tolerance for identifying approximate primal rays (certificate of dual infeasibility).
    • time_sec_limit: Time limit for the solver.
    • iteration_limit: Iteration limit for the solver.
    from mpax.utils import TerminationCriteria
    
    criteria = TerminationCriteria(
        eps_abs=1e-6,
        eps_rel=1e-6,
        eps_primal_infeasible=1e-8,
        eps_dual_infeasible=1e-8,
        time_sec_limit=60.0,
        iteration_limit=1000
    )
  11. Configure r2HPDHG solver hyperparameters

    main

    The r2HPDHG class accepts several configuration parameters to tune solver behavior. Below is a reference of the available attributes:

    ParameterTypeDefaultDescription
    verboseboolFalseEnable logging
    debugboolFalseEnable debug mode
    display_frequencyint10Frequency of iteration stats display
    jitboolTrueUse JAX JIT compilation
    unrollboolFalseUnroll loops
    termination_evaluation_frequencyint100Frequency of termination checks
    optimality_normfloatjnp.infNorm used for optimality
    eps_absfloat1e-4Absolute tolerance
    eps_relfloat1e-4Relative tolerance
    eps_primal_infeasiblefloat1e-8Primal infeasibility threshold
    eps_dual_infeasiblefloat1e-8Dual infeasibility threshold
    iteration_limitintmax_int32Maximum iterations
    l_inf_ruiz_iterationsint10Ruiz rescaling iterations
    l2_norm_rescalingboolFalseEnable L2 norm rescaling
    pock_chambolle_alphafloat1.0Pock-Chambolle parameter
    primal_importancefloat1.0Weighting for primal importance
    scale_invariant_initial_primal_weightboolTrueUse scale-invariant weights
    restart_schemeintRestartScheme.ADAPTIVE_KKTRestart strategy
    restart_to_current_metricintRestartToCurrentMetric.KKT_GREEDYMetric for restarts
    restart_frequency_if_fixedint1000Frequency for fixed restarts
    artificial_restart_thresholdfloat0.2Threshold for artificial restarts
    sufficient_reduction_for_restartfloat0.2Required reduction for restart
    necessary_reduction_for_restartfloat0.6Necessary reduction for restart
    primal_weight_update_smoothingfloat0.6Smoothing for weight updates
    adaptive_step_sizeboolTrueEnable adaptive step size
    adaptive_step_size_reduction_exponentfloat0.4Step size reduction exponent
    adaptive_step_size_growth_exponentfloat0.8Step size growth exponent
    adaptive_step_size_limit_coeffloat0.2Step size limit coefficient
    warm_startboolFalseUse warm start
    feasibility_polishingboolFalseEnable feasibility polishing
    eps_feas_polishfloat1e-06Polishing tolerance
    infeasibility_detectionboolTrueEnable infeasibility detection
  12. Create a Quadratic Program (QP) from arrays

    main

    Use create_qp to construct a boxed quadratic program from raw arrays. The function solves:

    $$\max \ \frac{1}{2}x^T Q x + c^T x$$ $$\text{s.t. } Ax = b$$ $$\quad \quad Gx \ge h$$ $$\quad \quad l \le x \le u$$

    Parameters:

    • Q: Quadratic term matrix (supports jnp.ndarray, BCOO, or BCSR).
    • c: Objective vector.
    • A: Equality constraint matrix.
    • b: Right-hand side of equality constraints.
    • G: Inequality constraint matrix.
    • h: Right-hand side of inequality constraints.
    • l: Lower bounds of variables.
    • u: Upper bounds of variables.
    • use_sparse_matrix: Boolean. If True (default), uses sparse formats for $Q$ and constraints.

    Returns:

    • A QuadraticProgrammingProblem object.
    from mpax.mp_io import create_qp
    import jax.numpy as jnp
    
    # Example setup
    Q = jnp.array([[1.0, 0.0], [0.0, 1.0]])
    c = jnp.array([1.0, 1.0])
    A = jnp.array([[1.0, 1.0]])
    b = jnp.array([1.0])
    G = jnp.array([[1.0, -1.0]])
    h = jnp.array([0.0])
    l = jnp.array([0.0, 0.0])
    u = jnp.array([1.0, 1.0])
    
    qp_problem = create_qp(Q, c, A, b, G, h, l, u)