# Rai Prescriptive Problem Formulation

> Formulates optimization and constraint satisfaction problems from ontology models — decision variables, constraints, objectives, and common patterns. Use for optimization and constraint-satisfaction tasks — building, reviewing, debugging, or relaxing an over-constrained formulation to resolve a reported conflict / IIS; then solve it with rai-prescriptive-solver-management. Not for interpreting or…

- **Type:** Skill
- **Install:** `agentstack add skill-relationalai-rai-agent-skills-rai-prescriptive-problem-formulation`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [RelationalAI](https://agentstack.voostack.com/s/relationalai)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [RelationalAI](https://github.com/RelationalAI)
- **Source:** https://github.com/RelationalAI/rai-agent-skills/tree/main/plugins/rai/skills/rai-prescriptive-problem-formulation
- **Website:** https://relational.ai

## Install

```sh
agentstack add skill-relationalai-rai-agent-skills-rai-prescriptive-problem-formulation
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Problem Formulation

> **Requires `relationalai>=1.11.0`.** The dual-guided multi-objective methods in this skill read solver sensitivity via `solve("highs", sensitivity=True)` (shadow prices as exact frontier slopes); earlier versions reject the request at the solver and omit the dual fields on `solve_info()`. See `rai-setup`.

## Summary

**What:** Optimization formulation — decision variables, constraints, objectives, and common problem patterns. Assumes a problem has already been selected via discovery.

**When to use:**
- Formulating variables, constraints, and objectives for a selected problem
- Reviewing or validating an existing formulation
- Translating business requirements into mathematical formulation
- Debugging formulations that would produce trivial or infeasible solutions (missing constraints, conflicting bounds, wrong aggregation scope)
- Choosing between variable types (continuous, integer, binary)
- Designing multi-concept coordination (flow networks, selection + quantity)

**When NOT to use:**
- Post-solve diagnosis of solutions that look wrong (all zeros, infeasible status, concentrated values) — see `rai-prescriptive-results-interpretation`
- Question discovery (what can this ontology answer, reasoner classification) — see `rai-discovery`
- PyRel syntax (imports, types, property patterns, stdlib) — see `rai-pyrel-coding`
- Ontology modeling or model enrichment (concept design, gap classification) — see `rai-ontology-design`
- Solver execution and diagnostics (solver selection, parameters, numerical stability) — see `rai-prescriptive-solver-management`
- Aggregation syntax (count/sum/per patterns) — see `rai-querying`

**Overview:**
1. Ground in the base ontology via `relationalai.semantics.inspect.schema(model)` — concepts, properties, types, relationships you're about to reference
2. Define decision variables (type, bounds, scope, naming)
3. Define constraints (forcing, capacity, balance, linking; validate interactions)
4. Define objective (direction, coefficients, multi-component handling)
5. Validate the complete formulation (structure, completeness, feasibility, data) — includes pre-solver audit that decision variables / constraints / objectives registered correctly
6. Solve and refine — diagnose surprising results via targeted `display(ref)` / `verify` / `solve_info`, simplify once correct, re-solve
7. Post-solve refinement — present results, surface user reactions, iterate on the formulation (see Workflow Step 7)

---

## Quick Reference — Problem API

```python
from relationalai.semantics.reasoners.prescriptive import Problem
from relationalai.semantics.std import aggregates as aggs

# Problem(model, Float) for MIP-style; Problem(model, Integer) for CSP-style (see [csp-formulation.md](references/csp-formulation.md)).
problem = Problem(model, Float)

# Decision variable — prefer scoped form (where=[...]) over unscoped.
# Capture the returned ref for targeted diagnostics later (model.select(x_flow.name, x_flow.lower, x_flow.upper).to_df(), etc).
x_flow = problem.solve_for(
    Lane.flow,
    where=[Lane.active],
    name=["item_id", "src_id"],
    lower=0.0,
    type="cont",
)

# Constraint — capture the ref to inspect just this constraint's grounded form
cap = problem.satisfy(
    model.require(
        aggs.sum(Lane.flow).per(Source).where(Lane.from_source(Source)) =1.13*) enum members, rendered by member name; on 1.12.0 itself pass the Python-side string `Member.name` instead (a member fails type inference there). |
| `satisfy` | `(expr, name=, keyed_by=)` | Add constraint. Returns `ProblemConstraint` — capture this ref to inspect the constraint's grounded form via `problem.display(ref)`. `keyed_by={"key": Concept}` declares the family's grounding keys as identifying entity back-pointers for post-solve readback — see `references/constraint-formulation.md` > Declaring constraint keys for post-solve readback. |
| `minimize` | `(expr, name=)` | Set minimization objective. Returns `ProblemObjective` — capture for targeted `display(ref)`. |
| `maximize` | `(expr, name=)` | Set maximization objective. Returns `ProblemObjective` — capture for targeted `display(ref)`. |
| `solve` | `(solver, time_limit_sec=, print_format=, ...)` | Execute solve. Solvers: `"highs"`, `"minizinc"`, `"ipopt"`, `"gurobi"`. `print_format` (`"moi"`, `"latex"`, `"mof"`, `"lp"`, `"mps"`, `"nl"`) populates `solve_info().printed_model` |
| `solve_info` | `()` | Post-solve summary (`termination_status`, `objective_value`, `solve_time_sec`, `num_points`, `solver_version`, `error`, `printed_model`). Has `.display()` method. |
| `verify` | `(*fragments)` | Post-solve check that the returned solution satisfies the original `Fragment`s at IC strictness (tighter than solver tolerance). Pass the original `model.require(...)` values, not `ProblemConstraint` refs. |
| `display` | `(part=None, *, where=None, limit=None, print_output=True)` | Print materialized formulation. `display()` for everything; `display(ref)` for one constraint or objective; `display(ref, where=)` scopes to filter-matching rows (`where=` requires `part`); `display(..., limit=N)` caps each table at top-N rows by `.name`. Variable subconcepts raise — query rows via `model.select(var.name, var.lower, var.upper).to_df()`. |
| `num_variables` / `num_constraints` / `num_min_objectives` / `num_max_objectives` | `()` | Engine-queryable counts; usable inside `model.require(...)` to assert formulation cardinality before solve |
| `Variable.values` | `(sol_index, value_ref)` | Property on `ProblemVariable`. Extracts solution values at `sol_index` (0-based), binding each value to `value_ref` (a `Float.ref()` or `Integer.ref()`). Use inside `model.select(...).where(var.values(sol_index, value_ref))`. Primary pattern for `populate=False` workflows. |
| `problem.variables` / `problem.constraints` / `problem.objectives` | (attributes) | Lists of registered refs in declaration order — iterate to walk an unfamiliar Problem |

---

## Formulation Workflow

**Interaction mode:** Before starting, ask the user which mode they prefer:
- **Guided** — present your proposed variables, constraints, and objective at each step and confirm before proceeding. Best when the user has domain context to share — problem framing involves subjective judgment (what's a hard constraint vs. a soft goal, how to scope variables, which business rules matter most).
- **One-shot** — produce the best formulation you can in a single pass. Best when the user wants speed and will review/iterate after.

After a question is selected (from question discovery) and the ontology is enriched (if needed), build the formulation in this order:

### Step 1: Ground in the base ontology

Prescriptive formulations reference concepts, properties, and relationships from an existing model. Before writing `solve_for` / `satisfy` / `minimize` / `maximize`, confirm every name and type you're about to use against the real schema:

```python
from relationalai.semantics import inspect

schema = inspect.schema(model)

# Every concept referenced in the formulation
for name in referenced_concepts:
    assert name in schema, f"Concept {name} not in model"
    props = schema[name].properties
    # surface any properties used in bounds, constraints, or the objective
```

Catches two silent-failure modes that account for most prescriptive errors:

1. **Hallucinated surface** — `Customer.tier` in a constraint when the real property is `Customer.category`. Solver happily runs on wrong variables and returns nonsense.
2. **Wrong-type inference** — using an `Integer` property as if it were `Float` (or vice versa) when the type now propagates from `TableSchema`. Silent coercion masks incorrect bound derivation.

**Reuse upstream derived surface.** If rai-rules-authoring or rai-graph-analysis ran earlier in the same session, the subtypes and properties they wrote back (classification subtypes, centrality scores, derived flags) are already in the model — they show up in `inspect.schema(model)`. Reference them directly in `solve_for` / `satisfy` / `minimize` / `maximize` rather than re-deriving their logic in the formulation. See [examples/chained_rules_prescriptive.py](examples/chained_rules_prescriptive.py).

**Confirm modeled dimensions.** If the problem spans periods/scenarios/categories, that dimension must exist in the ontology — as a Concept (`Week`, `Period`, `Scenario`), as an `Integer` reference scoped via `std.common.range()`, or, for a small closed vocabulary, as a `model.Enum` (*requires relationalai>=1.12*; enum-indexed `solve_for` creates one binary per (entity, member) — see `references/csp-formulation.md` § Enum-indexed decision) — before Step 3. Constraints over the dimension are then a single declarative expression that quantifies automatically. **Never** plan to iterate over the dimension in Python or enumerate its values by hand; both bypass the engine. See `rai-ontology-design` if enrichment is needed.

**When to skip:** this step is cheap but not free. Skip on small greenfield models or one-shot formulations where the model fits in a single code block you just wrote.

### Step 2: Define Variables
What decisions are being made? What can the solver control?
- Start with the base model context — examine concepts, properties, relationships
- Identify the primary decision entity first, then auxiliary/aggregation variables
- Choose variable types (continuous/integer/binary) and set bounds from data
- For minimize objectives, include a slack/unmet variable to avoid infeasibility

See [variable-formulation.md](references/variable-formulation.md) for context integration, variable principles, and advanced patterns (types, bounds, entity creation, slack, parametric/time-indexed).

### Step 3: Define Constraints
What rules must the solution satisfy?
- Start with model structure + user goals
- For multi-period or recurrence patterns (inventory balance, sequencing, cumulative state), use declarative quantification: `Concept.ref()` adjacency joins (`w_prev.num == w.num - 1`) or `where=[t == std.common.range(start, stop)]` — single `satisfy()` call grounds across all periods. See [constraint-formulation.md](references/constraint-formulation.md) > Temporal recurrence and [examples/multi_period_flow_conservation.py](examples/multi_period_flow_conservation.py).
- Add forcing constraints first — these prevent trivial zero solutions for minimize objectives
- Add capacity/resource constraints from data properties
- Add flow conservation if the model has network structure
- Derive parameters from data ranges, not arbitrary values

### Step 4: Define Objective(s)
What are we optimizing?
- Start with user's stated goal — map business language to minimize/maximize. See [objective-formulation.md](references/objective-formulation.md) for direction, multi-component objectives, and penalty terms
- Reference defined variables and data properties in the expression
- Check for trivial solution risk: "If all variables = 0, are all constraints satisfied?" If yes, Step 3 needs a forcing constraint.
- **Competing objectives?** If the formulation has a penalty term bundling two concerns (cost + penalty×slack), or a constraint that represents a competing goal (return ≥ threshold), consider whether the user wants to explore the tradeoff rather than fix a single point. See [multi-objective-formulation.md](references/multi-objective-formulation.md) for the epsilon constraint approach.
- **Parameter sensitivity / what-if?** If key constraints use fixed values that could vary (budget, demand, service level), use the Scenario Concept pattern — parameterize the constraint, index the decision variable by Scenario, and solve all scenarios in a single solve. This keeps results in the ontology and avoids manual re-solve loops. See [scenario-analysis.md](references/scenario-analysis.md).

### Step 5: Validate
Is the formulation complete and correct?
- Every variable appears in at least one constraint or the objective
- Every constraint references at least one decision variable
- Join paths in `.where()` clauses connect to actual data
- Bounds are consistent (lower ` subconcept for each decision variable). They appear in `inspect.schema(model).concepts`:

```python
from relationalai.semantics import inspect

schema = inspect.schema(model)
variables   = [c for c in schema.concepts if "Variable" in c.extends]
constraints = [c for c in schema.concepts if c.name == "Constraint" or "Constraint" in c.extends]
objectives  = [c for c in schema.concepts if c.name == "Objective"  or "Objective"  in c.extends]

# Confirm one Variable_ per solve_for call, one Constraint_ per satisfy,
# one Objective_ per minimize/maximize.
```

**(b) Binding cardinality.** Registration does NOT mean the variable binds to any rows. A `solve_for(..., where=[always_false])` still registers a `Variable_` subconcept but has zero bindings — the solver will run on an empty decision set. Check each `Variable_` for non-empty binding:

```python
for var_concept in variables:
    resolved = model.concept_index[var_concept.name]
    n = len(model.select(resolved).to_df())
    if n == 0:
        # The where= clause excluded every row. Fix the predicate
        # (wrong property name, wrong threshold, missing join) and re-check.
        raise ValueError(f"{var_concept.name} has 0 bindings")
```

**(c) Coefficient presence.** Properties referenced as objective or constraint coefficients must be populated by `model.define(...)` — an unbound coefficient Property has no tuples, so the join produces no row and the term doesn't appear in the formulation (PyRel relational semantics). The solver returns OPTIMAL with a vacuous objective. Distinct from (b): (b) catches empty *decision-variable scope*, (c) catches unbound *coefficient data*. Both surface as OPTIMAL with `obj=0`.

```python
for coef_prop in objective_coefficient_properties:
    n = len(model.select(coef_prop).to_df())
    if n == 0:
        raise ValueError(f"Coefficient {coef_prop} has no tuples — objective term won't ground")
```

**(d) Constraint binding cardinality.** Capture each `satisfy()` return value at declaration time, then verify the constraint grounded on the expected number of groupings before solving. Distinct from (b)/(c): (b) catches empty variable scope from a `where=` predicate, (c) catches unbound coefficients, (d) catches per-grouping bodies that didn't ground because a referenced bound was empty for some entities — under PyRel relational semantics an empty body produces no row, so the constraint applies to fewer entities than the `.per(Entity)` syntax suggests, and `OPTIMAL` returns with the entities that got no row effectively unconstrained.

```python
cap_constr = problem.satisfy(model.require(usage  Targeted Inspection.

**Simplify once correct:**
- Static parameters over dynamic calculations
- Objective terms for goals; constraints for hard requirements
- Group-level constraints over pairwise/granular combinations

Loop until the result is correct, fast enough, and defensible enough to take to Step 7. See [diagnostic-workflow.md](references/diagnostic-workflow.md) for failure-mode-by-failure-mode guidance and [formulation-simplification.md](references/formulation-simplification.md) for the simplification patterns in depth.

### Step 7: Present, React, Refine
Is the formulation complete — including constraints the user couldn't articulate upfront?
- Solve and present the result to the user (see Constraint Elicitation > Post-Solve: Iterative Refinement)
- Use the result as an elicitation tool to surface latent preferences
- Disambiguate rejections into constraint types, add them, re-solve
- Repeat until the user accepts or feasibility pressure forces prioritization

Steps 1-6 produce the best formulation you can build from what the user has told you. Step 7 discovers what they couldn't tell you until they saw a concrete result. Most real-world formulations require at least one pass through Step 7.

---

## Formul

…

## Source & license

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

- **Author:** [RelationalAI](https://github.com/RelationalAI)
- **Source:** [RelationalAI/rai-agent-skills](https://github.com/RelationalAI/rai-agent-skills)
- **License:** Apache-2.0
- **Homepage:** https://relational.ai

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

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** yes

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-relationalai-rai-agent-skills-rai-prescriptive-problem-formulation
- Seller: https://agentstack.voostack.com/s/relationalai
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
