AgentStack
SKILL verified Apache-2.0 Self-run

Rai Prescriptive Solver Management

skill-relationalai-rai-agent-skills-rai-prescriptive-solver-management · by RelationalAI

Covers solver lifecycle including problem type classification, solver selection and creation, global constraints, pre-solve validation, solve execution (including requesting sensitivity and conflict / IIS diagnostics), and solver-level diagnostics. Use when configuring or running optimization solvers, not for interpreting post-solve results (see rai-prescriptive-results-interpretation).

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-relationalai-rai-agent-skills-rai-prescriptive-solver-management

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Rai Prescriptive Solver Management? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Solver Management

> Requires relationalai>=1.11.0. Requesting sensitivity and conflict / IIS diagnostics on solve() requires the dual-bearing solver; earlier versions reject sensitivity=True as an unsupported solver attribute. See rai-setup.

Summary

What: Solver lifecycle — selection, creation, formulation inspection, execution, and diagnostics.

When to use:

  • Choosing which solver to use for a problem (HiGHS vs Gurobi vs MiniZinc vs Ipopt)
  • Setting up Problem and Solver instances
  • Inspecting formulation before solving (problem.display(), variable/constraint counts)
  • Tuning solver parameters (time limits, MIP gap, presolve)
  • Diagnosing solver-level failures (crashes, numerical instability, Big-M sizing)
  • Understanding solver performance (slow convergence, presolve tuning)
  • Running parametric/scenario solves

When NOT to use:

  • Post-solve result interpretation and communication — presenting OPTIMAL/INFEASIBLE/DUALINFEASIBLE/TIMELIMIT status to users, solution quality assessment, trivial solution detection, sensitivity analysis, and root-cause diagnosis of infeasibility/unboundedness — see rai-prescriptive-results-interpretation.
  • Variable/constraint/objective formulation patterns — see rai-prescriptive-problem-formulation
  • PyRel syntax (imports, types, properties) — see rai-pyrel-coding

Overview:

  1. Understand the optimization goal — what decisions are being made, what does success look like?
  2. Classify the problem type (LP / MILP / QP / NLP / CSP)
  3. Select a solver (decision rules based on variable types, nonlinearity, and license availability)
  4. Create Problem and Solver instances
  5. Validate formulation pre-solve (problem.display(), count checks)
  6. Execute solve (parameters, time limits, warm starting)
  7. Diagnose solver-level issues (crashes, numerical instability, performance)

Quick Reference

from relationalai.semantics import Float, Integer, Model, sum
from relationalai.semantics.reasoners.prescriptive import Problem

# 1. Create Problem (Float for LP/MILP/NLP, Integer for CP)
problem = Problem(model, Float)

# 2. Register variables (type: "cont", "int", "bin"; bounds; naming)
problem.solve_for(Route.x_flow, type="cont", lower=0, upper=Route.capacity,
            name=["flow", Route.origin, Route.dest])
problem.solve_for(Facility.x_open, type="bin", name=["open", Facility.id])

# 3. Add constraints (model.require inside problem.satisfy)
problem.satisfy(model.require(sum(Route.x_flow).per(Customer) >= Customer.demand))
problem.satisfy(model.require(Route.x_flow  0)
model.require(problem.num_constraints() > 0)

# 6. Solve — solver choice depends on problem type and user license
#    See "Solver Selection" section for decision rules
problem.solve(solver_name, time_limit_sec=120)
model.require(problem.termination_status() == "OPTIMAL")
problem.solve_info().display()
# Solvers: "highs" (LP/MILP, open-source), "gurobi" (LP/MILP/QP/QCP, license required),
#          "minizinc" (CP, open-source), "ipopt" (NLP, open-source)
# Check: problem.termination_status() → "OPTIMAL" | "INFEASIBLE" | "DUAL_INFEASIBLE" | "TIME_LIMIT"

Post-Solve API

problem.solve() returns None -- do NOT assign its return value. Access solve results through separate methods:

  • Status summary: problem.solve_info().display() prints a human-readable solve summary.
  • Status properties: problem.solve_info().termination_status (str), problem.solve_info().objective_value (float).
  • Solution values (populate=True): Query via model.select() — solved values are written back into model properties.
  • Solution values (populate=False): Use Variable.values(sol_index, value_ref) on the ProblemVariable returned by solve_for().
# CORRECT usage
problem.solve("highs", time_limit_sec=60)

si = problem.solve_info()
si.display()                    # Print status summary
print(si.termination_status)    # "OPTIMAL", "INFEASIBLE", etc.

# Always check termination_status before reading objective_value or extracting
# results — si.objective_value is None on infeasible / unbounded solves and will
# silently render as "None" in print() / math / comparisons otherwise.
if si.termination_status in ("OPTIMAL", "LOCALLY_SOLVED"):
    print(si.objective_value)   # Objective function value

# For populate=False workflows, use Variable.values() (gate the extraction on
# termination_status for the same reason):
assign_var = problem.solve_for(Assignment.x, type="bin", populate=False)
problem.solve("highs")
si = problem.solve_info()
if si.termination_status in ("OPTIMAL", "LOCALLY_SOLVED"):
    value_ref = Float.ref()
    df = model.select(
        assign_var.assignment.worker.name.alias("worker"),
        value_ref.alias("value"),
    ).where(assign_var.values(0, value_ref), value_ref > 0.5).to_df()

> Warning: result = problem.solve(...); result.status fails because solve() returns None regardless of solver. Accessing any attribute on None raises AttributeError. Always call problem.solve() on its own line, then use problem.solve_info() separately.


Read the Formulation

Before classifying or configuring, read the existing formulation (built in rai-prescriptive-problem-formulation) to extract solver-relevant characteristics:

  1. Variable types — Are there integer or binary variables? (determines solver compatibility)
  2. Objective direction — Minimize or maximize? Is feasibility the primary concern or optimality? (determines parameter tuning)
  3. Problem structure — Are there nonlinear terms? How many entities and constraints? (determines solver choice and time limits)

Problem Type Classification

Identify the problem type before choosing a solver:

Linear Programming (LP): All variables continuous, objective and constraints all linear. No products of variables, no nonlinear functions.

Mixed-Integer Linear Programming (MILP): Some variables integer or binary, but objective and constraints remain linear. No products of integer variables (that makes it nonlinear). Keep Big-M tight.

Quadratic Programming (QP): Quadratic terms in objective only (e.g., risk minimization with covariance). Constraints remain linear. Check convexity (Q matrix positive semi-definite) for global optimum.

Quadratically Constrained Programming (QCP): Quadratic terms in constraints (e.g., norm constraints). More restrictive solver requirements than QP. Check if constraints are convex.

Nonlinear Programming (NLP): Nonlinear functions (exp, log, sqrt, sin, cos). Integer + NLP is very difficult (MINLP). May have local optima -- solution may not be global.

Constraint Satisfaction Problem (CSP): No meaningful objective function. Goal is finding any feasible solution. Often discrete variables with combinatorial constraints. Benefits from global constraints.


Solver Selection

Choose the solver based on variable types and objective/constraint structure:

Support Matrix

| Problem Type | Gurobi | HiGHS | Ipopt | MiniZinc | |---|---|---|---|---| | Linear Programs (LP) | YES | YES | YES | NO | | Mixed-Integer Linear (MILP) | YES | YES | NO | NO | | Quadratic Programs (QP) | YES (convex obj only) | YES | YES | NO | | Quadratically Constrained (QCP) | YES | NO | YES | NO | | Nonlinear Programs (NLP) | YES | NO | YES | NO | | Constraint Programming (CP) | NO | NO | NO | YES | | Discrete Variables (int/bin) | YES | YES | NO | YES | | Continuous Variables | YES | YES | YES | NO |

Solver Profiles

HiGHS (solver="highs"): Open-source. Best for LP, MILP, convex QP objectives. Fast simplex/IPM LP solver, good MILP for moderate problems. Params: time_limit, mip_rel_gap, presolve ("choose"/"on"/"off"), threads.

HiGHS limitations (specific):

  • No indicator constraints — implies() will fail. Use Big-M reformulation instead.
  • No SOS constraints — special_ordered_set_type_1() / special_ordered_set_type_2() will fail. Use explicit binary variable formulations instead.
  • No quadratic constraints (QCP) — quadratic terms in constraints will fail. Only convex quadratic objectives (QP) are supported.
  • No nonlinear functions (math.exp, math.log, math.sqrt, x**n) — use Gurobi (preferred when licensed) or Ipopt.

Gurobi (solver="gurobi"): Commercial (available via RAI). Best for large-scale MILP, QP, QCP, and nonlinear problems. Industry-leading MILP performance, discrete + continuous + quadratic + nonlinear (math.exp, math.log, math.sqrt, x ** n), excellent diagnostics, multi-objective support. Params: TimeLimit, MIPGap, MIPFocus (0=balanced, 1=feasibility, 2=optimality, 3=bound), Presolve (2 for aggressive), Threads (0 for auto). License required: Gurobi requires a named prescriptive engine with a Snowflake secret and external access integration configured in raiconfig.yaml. See [rai-setup](../rai-setup/SKILL.md) for setup. If unavailable, fall back to HiGHS (LP/MILP) or Ipopt (NLP). Large MIP problems may solve significantly faster with Gurobi than HiGHS.

MiniZinc (solver="minizinc"): Open-source (Chuffed backend). Best for CP, combinatorial, constraint satisfaction. Powerful propagation, native support for all_different and implies, multiple solutions via solution_limit. Params: time_limit_sec, solution_limit. Cannot handle continuous variables, LP, QP, NLP, or SOS constraints. MiniZinc's native global-constraint library is far richer (circuit, cumulative, element, ...) but only all_different and implies are exposed from PyRel to MiniZinc today; SOS1/SOS2 are PyRel-exposed but Gurobi-only.

MiniZinc-specific requirements and behavior:

  • Requires Problem(model, Integer) — a single Float decision variable or Float data coerces the problem to MIP and MiniZinc will reject it.
  • solution_limit=K enables multi-solution enumeration. Termination status SOLUTION_LIMIT (not OPTIMAL) when search stopped at K with more feasible remaining; OPTIMAL means search exhausted with ≤ K found.
  • Chuffed is the only backend exposed today; Gecode / CP-SAT are not selectable.
  • Native MiniZinc logging is NOT captured into LogMessage events. Use log_to_console=True to stream logs.

For the full CSP-style formulation guide, see [csp-formulation.md](../rai-prescriptive-problem-formulation/references/csp-formulation.md).

Ipopt (solver="ipopt"): Open-source. Best for continuous nonlinear optimization. Interior-point for smooth NLP, handles nonlinear objectives AND constraints. Finds local optima only. Params: max_iter, max_wall_time, tol (e.g. 1e-8), print_level, mu_strategy. Cannot handle integer or binary variables -- will FAIL.

Decision Rules

Use these rules in order to pick a solver. Gurobi outperforms open-source solvers (HiGHS, Ipopt) on every problem type it supports — faster solve times, tighter MIP gaps, better scaling. Always prefer Gurobi when the user has a license. Only recommend open-source when Gurobi is unavailable or the problem type requires it (CSP → MiniZinc, smooth NLP → Ipopt).

  1. Check variable types first.
  • Any integer/binary variable? Ipopt is invalid. Gurobi preferred; HiGHS if no license.
  • MiniZinc only if the problem is pure constraint satisfaction with discrete variables.
  1. Check for nonlinearity.
  • math.exp(x), math.log(x), math.sqrt(x), or x ** n?

HiGHS and MiniZinc are invalid.

  • Continuous-only NLP: Gurobi (preferred when licensed) or Ipopt (best for smooth local NLP).
  • Discrete + nonlinear: Gurobi only.
  • Trig (math.sin, math.cos, etc.) and division between two decision variables are not lowered to the solver by the prescriptive library — reformulate as piecewise-linear approximations or via auxiliary variables.
  1. Check for quadratic constraints.
  • Quadratic terms in constraints (not just objective): HiGHS is invalid.
  • Gurobi preferred; Ipopt if continuous-only and no Gurobi license.
  1. No objective (constraint satisfaction)?
  • Discrete: MiniZinc (purpose-built for CSP).
  • Otherwise Gurobi or HiGHS can find feasibility.

Quick reference:

| Your problem has... | Gurobi available | No Gurobi license | |---|---|---| | Binary/integer + linear (MILP) | Gurobi | HiGHS | | Binary/integer + quadratic (MIQP) | Gurobi | (no open-source alternative) | | Continuous + linear (LP) | Gurobi | HiGHS | | Continuous + quadratic objective (QP) | Gurobi | HiGHS | | Continuous + quadratic constraints (QCP) | Gurobi | Ipopt | | Continuous + nonlinear (NLP) | Gurobi or Ipopt | Ipopt | | Discrete + constraint satisfaction (CSP) | MiniZinc | MiniZinc | | Need multiple solutions | MiniZinc (best) | MiniZinc |

Problem size guidelines (small/medium/large thresholds) and Problem initialization patterns (Problem(model, Float) vs Integer, solver string names) are in [solver-details.md](references/solver-details.md). Always select solver based on problem type and confirm license availability.


Global Constraints

Global constraints (all_different, implies, SOS1, SOS2) provide solver-exploitable combinatorial structure. Each has specific solver requirements:

| Constraint | Requires | Alternatives | |---|---|---| | all_different | MiniZinc | O(n^2) pairwise inequalities in MIP | | implies | Gurobi or MiniZinc | Big-M reformulation for HiGHS | | special_ordered_set_type_1 | Gurobi | Binary variables + sum constraints | | special_ordered_set_type_2 | Gurobi | Binary segment selection variables |

For syntax, code examples, and a CP vs MIP decision guide, see [global-constraints.md](../rai-prescriptive-problem-formulation/references/global-constraints.md).


Formulation Display

Use problem.display() to inspect variables, objectives, and constraints before solving. Check problem.num_variables(), problem.num_constraints(), and problem.num_min_objectives() / problem.num_max_objectives() against expectations (these are Relationships — use in model.require() or model.select()). Use problem.display(part) for targeted inspection of a single constraint or objective; for variables, query rows via model.select(var_ref.name, var_ref.lower, var_ref.upper).to_df(). Use problem.printed_model() (Relationship, with print_format= on problem.solve()) to get LP/MPS/LaTeX text representations. The same Problem can be re-solved multiple times — constraints accumulate across calls.

See [formulation-display.md](references/formulation-display.md) for display output structure, diagnostic tables, and targeted inspection patterns.


Pre-Solve Validation

Run five checks before calling problem.solve(): (1) entity population — problem.num_variables() > 0; (2) constraint population — problem.num_constraints() > 0 with at least one forcing constraint; (3) objective — exactly one minimize/maximize for an optimization (or sensitivity=True) solve, zero for a pure feasibility / CSP solve (conflict=True is objective-agnostic: it needs none, but tolerates the objective of an infeasible optimization model you're diagnosing); (4) data integrity — no nulls, no negatives in costs/capacities, total capacity >= total demand; (5) formulation structure via problem.display().

# Minimum pre-solve checklist
problem.display()
model.require(problem.num_variables() > 0)
model.require(problem.num_constraints() > 0)
model.require(problem.num_min_objectives() + problem.num_max_objectives() == 1)  # optimization/sensitivity; use == 0 for pure feasibility/CSP (conflict=True works with either)

See [pre-solve-validation.md](references/pre-solve-validation.md) for full checks, diagnostic queries, and data integrity patterns.


Common Compilation Errors

For prescriptive-context compile errors (entity reference passed as scalar, zero entities, type mismatch, undefined concept), see [compilation-errors.md](references/compilation-errors.md). G

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.