Skip to content

Problem

relationalai.semantics.reasoners.prescriptive.problem
Problem(model: b.Model, numeric_type: b.Concept)

Define and solve a decision problem on a model.

Use Problem.solve_for to declare decision variables, Problem.minimize/ Problem.maximize to add objectives, and Problem.satisfy to add constraints. Then call Problem.solve and read results via populated properties (the default — e.g. model.select(X.v)), the Variable.values Property for engine-side queries (model.select(sol_idx, val).where(var.values(sol_idx, val))), or Problem.solve_info for a Python-side metadata snapshot. Pass solve(sensitivity=True) for post-solve duals (reduced costs, shadow prices, basis statuses) or solve(conflict=True) to diagnose an infeasible model (an irreducible infeasible subsystem); see Problem.solve and the Variable / Constraint accessors below.

  • model

    (Model) - The Model to attach solver constructs (variables, objectives, constraints, and results) to.
  • numeric_type

    (Concept) - Numeric type used for bounds, solution values, and numeric literals. Use semantics.frontend.core.Float for HiGHS/Gurobi/Ipopt (even for MIPs), and semantics.frontend.core.Integer for MiniZinc.

Declare a variable and objective:

from relationalai.semantics import Float, Model
from relationalai.semantics.reasoners.prescriptive import Problem
m = Model("demo")
x = m.Relationship(f"{Float:x}")
problem = Problem(m, Float)
problem.solve_for(x, name="x", lower=0)
problem.minimize(x)

Calling Problem.solve invalidates the Python-side solve_info() cache. Result accessors like termination_status() return engine-side Relationships that reflect the most recent successful solve.

Problem.variables: list[ProblemVariable]

Variable components registered via Problem.solve_for.

Problem.objectives: list[ProblemObjective]

Objective components registered via Problem.minimize / Problem.maximize.

Problem.constraints: list[ProblemConstraint]

Constraint components registered via Problem.satisfy.

Problem.Variable: Concept

Engine-side aggregate Concept covering all declared decision variables. Use it to query across all variables of this problem — for example, model.select(p.Variable.name, p.Variable.lower).where(p.Variable) to list every variable’s name and lower bound. After solve(sensitivity=True) / solve(conflict=True) it also carries reduced_cost, basis_status, and the *_in_conflict predicates (see ProblemVariable).

Problem.Objective: Concept

Engine-side aggregate Concept covering all objectives. Each ProblemObjective extends this Concept; use it to query names, types, and (in future) duals or printed expressions.

Problem.Constraint: Concept

Engine-side aggregate Concept covering all constraints. Each ProblemConstraint extends this Concept; use it to query names and types, and — after solve(sensitivity=True) / solve(conflict=True)shadow_price, basis_status, and in_conflict (see ProblemConstraint).

Problem.solve_for(
expr: b.Relationship | b.Chain | b.Expression,
where: Optional[list[Any]] = None,
populate: bool = True,
name: Optional[Any | list[Any]] = None,
type: Optional[str] = None,
lower: Optional[std.NumberValue] = None,
upper: Optional[std.NumberValue] = None,
start: Optional[std.NumberValue] = None,
) -> "ProblemVariable"

Declare decision variables for the problem.

Call this before adding objectives or constraints. The returned ProblemVariable IS a Concept — use it directly with model.define(), model.select(), and .ref() to annotate and query declared variables.

Parameters:

  • expr

    (Relationship or Chain or Expression) - Expression describing the variable(s) to create (for example, a scalar relationship like x or an indexed property like Item.cost).
  • where

    (list[Any], default: None) - Optional conditions restricting which variable instances are created.
  • populate

    (bool, default: True) - If True (default), write solved values back to the original relationship/property after Problem.solve. Set to False when you create multiple Problem instances that solve for the same relationship on the same model.
  • name

    (Any or list[Any], default: None) - Display name for variables. Use a string for scalars or a list pattern for indexed variables (for example, ["x", Item.i]).
  • type

    (str, default: None) - Variable type: "cont" (default for Float), "int" (default for Integer), or "bin" (binary 0/1).
  • lower

    (Variable or float or int or Decimal, default: None) - Lower/upper bounds and an optional initial value hint.
  • upper

    (Variable or float or int or Decimal, default: None) - Lower/upper bounds and an optional initial value hint.
  • start

    (Variable or float or int or Decimal, default: None) - Lower/upper bounds and an optional initial value hint.

Returns:

  • ProblemVariable - The variable subconcept. Annotate via model.define(var.lower(0)), query values after solve via model.select(sol_idx, val).where(var.values(sol_idx, val)).

    Back-pointer fields. The subconcept exposes one property per non-value field of the underlying relationship, named after the field’s own name (the explicit :name from the format string, or the lowercased type name if no explicit name was given). Use these to query “which domain entity does this variable represent?”. Examples for the common shapes:

    # Entity property: f"{Queen} is in {Integer:column}"
    # → var.queen (back to the Queen instance)
    var = p.solve_for(Queen.column, ...)
    model.select(sol_idx, var.queen.row, val).where(var.values(sol_idx, val))
    # Explicit field name: f"{Edge:e} has {Float:flow}"
    # → var.e (uses the explicit name, not "edge")
    var = p.solve_for(Edge.flow, ...)
    model.select(sol_idx, var.e.id, val).where(var.values(sol_idx, val))
    # Multi-arity entity property:
    # f"{Player} in {Integer:week} is in {Integer:group}"
    # → var.player + var.week
    var = p.solve_for(Player.assign(w, x), ...)
    model.select(sol_idx, var.player.p, var.week, val).where(var.values(sol_idx, val))
    # Bare multi-arity (no entity):
    # f"cell {Integer:i} {Integer:j} is {Integer:x}"
    # → var.i + var.j
    var = p.solve_for(cell(i, j, x), ...)
    model.select(sol_idx, var.i, var.j, val).where(var.values(sol_idx, val))
    # Bare scalar relationship: f"{Float:x}" — no back-pointer.
    # Query var.values directly.
    var = p.solve_for(x, name="x", ...)
    model.select(sol_idx, val).where(var.values(sol_idx, val))

Raises:

  • ValueError - If variables are already defined for this relationship, if an argument has an invalid value (for example, an unknown type), or if a non-value field name would shadow an intrinsic attribute on the Variable subconcept. Shadow categories include engine-side Properties (name, type, lower, upper, start, values), Python @property descriptors (dsl_expr, concept_name, property_name, var_type, var_where, populate), and Concept.RESERVED_NAMES methods (ref, new, alias, where, select, define, require, etc.). See Problem._reserved_variable_field_names for the authoritative list — the error message lists the full set. Rename the conflicting field in the relationship’s format string. A ValueError is also raised if the value field’s declared type (Integer / Float) does not match the Problem’s numeric type: a decision variable’s value field must match Problem(model, …); set integrality with type='int' / 'bin', not the property type.
  • TypeError - If an argument has an invalid type.

Referenced By:

RelationalAI Documentation
├──  Build With RelationalAI
│   └──  Understand how PyRel works > Use advanced reasoning > Prescriptive reasoning > Solve a decision problem
│       ├──  Overview
│       │   └──  How solving a decision problem works
│       ├──  Create a Problem object
│       │   └──  Choose a default numeric type
│       ├──  Add decision variables
│       │   ├──  Declare decision variables
│       │   └──  Choose variable types and bounds
│       ├──  Solve a decision problem
│       │   └──  Avoid common pitfalls
│       └──  Work with solutions
│           └──  Determine how to access results
└──  Release Notes
    └──  Python API Release Notes
        └──  What’s New in Version 1.2.0
            └──  Bug Fixes
Problem.minimize(
expr: b.Variable | float | int | b.Fragment, name: Optional[Any | list[Any]] = None
) -> "ProblemObjective"

Add a minimization objective.

The expression must reference at least one decision variable declared via Problem.solve_for.

Parameters:

  • expr

    (Variable or float or int or Fragment) - Objective expression to minimize.
  • name

    (Any or list[Any], default: None) - Optional objective name (string for scalar, or a list pattern for indexed objectives).

Returns:

  • ProblemObjective - The objective subconcept (IS a Concept).

Raises:

  • ValueError - If the objective does not reference any declared decision variables.

Notes:

Under PyRel relational semantics, an objective expression whose body evaluates to an empty relation produces no row, so no objective is registered for that call. The behavior is intentional but can be surprising when the modeler expected the objective to apply. It covers:

  • sum(X.v).where(<filter matching no rows>)
  • sum(select(X.v).where(<empty>)) — filter encapsulated inside the aggregate body
  • sum(model.union(<all-empty branches>))
  • mixed shapes like sum(<empty>) + sum(<populated>) — when any sub-aggregate’s body is empty, the arithmetic produces no row

For per-grouping objectives (...per(X.group).where(...)) the behavior is per-grouping: the objective registers for groupings whose body has rows; groupings whose body is empty produce no row, so no per-grouping objective registers for them.

Problem.num_min_objectives returns the count as a Relationship. To read it as a Python scalar, query the model: n = model.select(p.num_min_objectives()).to_df().iloc[0, 0].

Problem.maximize(
expr: b.Variable | float | int | b.Fragment, name: Optional[Any | list[Any]] = None
) -> "ProblemObjective"

Add a maximization objective.

The expression must reference at least one decision variable declared via Problem.solve_for.

Parameters:

  • expr

    (Variable or float or int or Fragment) - Objective expression to maximize.
  • name

    (Any or list[Any], default: None) - Optional objective name (string for scalar, or a list pattern for indexed objectives).

Returns:

  • ProblemObjective - The objective subconcept (IS a Concept).

Raises:

  • ValueError - If the objective does not reference any declared decision variables.

Notes:

See Problem.minimize Notes — empty-body relational semantics apply identically here. Problem.num_max_objectives returns the count as a Relationship; query as a scalar via model.select(p.num_max_objectives()).to_df().iloc[0, 0].

Problem.satisfy(
expr: b.Fragment,
name: Optional[Any | list[Any]] = None,
keyed_by: Optional[dict[str, Any]] = None,
) -> "ProblemConstraint"

Add constraints from a model.require(...) fragment.

Use this to turn a require-clause fragment into solver constraints. The returned ProblemConstraint IS a Concept — annotate via model.define(constr.name("budget")), query via model.select(constr.name).where(constr).

Passing a model.require(...) fragment to satisfy detaches it from the model’s active integrity constraints — the solver enforces it instead, and it no longer fires engine-side. To also check it engine-side against the current solution after Problem.solve, call Problem.verify — it temporarily reinstalls the fragment as an IC, evaluates it, and removes it (one-shot).

.. note:

LP and MIP solvers return floating-point solutions that satisfy
constraints within solver tolerance (e.g. ``1e-8``), but engine
ICs check exact inequality. For continuous-variable constraints,
use a tolerant ``model.require()`` post-solve instead
(e.g. ``model.require(x <= bound + 1e-6)``).

Parameters:

  • expr

    (Fragment) - A fragment created by Model.require (optionally scoped with Model.where).
  • name

    (Any or list[Any], default: None) - Optional constraint name (string for scalar, or a list pattern for indexed constraints).
  • keyed_by

    (dict[str, ref], default: None) - Maps a back-pointer name to the grounding reference each constraint instance is 1:1 with. The reference is an entity ({"shift": Shift}, so con.shift joins to the shift’s data) or a value such as an identifier ({"i": X.i}, so con.i is the scalar index); a bare primitive type is rejected. Each entry becomes an identifying Property on the constraint, so the instance is identified by — and joins to the model by — its key (con.shift.min_coverage), the same way a variable points back to its entity (var.food). The keys must uniquely determine the constraint: a key set that does not (including two conjuncts of a single require(A, B) sharing keys) makes two instances collide on one identity and raises at solve (an engine-side functional-dependency error). name= plays no part in this identity — it is a display label only; a family is read back by key only if keyed_by declares one. Key names must be valid identifiers and must not clash with the managed constraint properties (root, name, type, shadow_price, basis_status, in_conflict, id). Omit it for constraints whose marginals you do not read back by key.

Returns:

  • ProblemConstraint - The constraint subconcept (IS a Concept).

Raises:

  • TypeError - If expr is not a fragment.
  • ValueError - If the fragment has no require clause, or if it includes select/define clauses; if a keyed_by name is not an identifier, clashes with a managed constraint property, or its reference is neither an entity nor a value reference; or, at solve, if the keyed_by key set is not 1:1 with the constraint instances (an engine-side functional-dependency error).

Notes:

Under PyRel relational semantics, a require clause whose body evaluates to an empty relation produces no row, so no constraint is registered for that call. The behavior is intentional but can be surprising when the modeler expected the constraint to apply. It covers:

  • sum(X.v) <= sum(X.v).where(<empty filter>)
  • filter-encapsulated empties like sum(select(X.v).where(<empty>)) <= 5
  • all-empty-branch unions like sum(model.union(<empty>, <empty>)) <= 5
  • any arithmetic combination where one operand’s body is empty

For per-grouping constraints (...per(X.group).where(...)) the behavior is per-grouping: the constraint registers for groupings whose body has rows; groupings whose body is empty produce no row, so no per-grouping constraint registers for them.

Problem.num_constraints returns the count as a Relationship. To read it as a Python scalar, query the model: n = model.select(p.num_constraints()).to_df().iloc[0, 0].

Referenced By:

RelationalAI Documentation
└──  Build With RelationalAI
    └──  Understand how PyRel works > Use advanced reasoning > Prescriptive reasoning > Solve a decision problem
        ├──  Overview
        └──  Add constraints
            ├──  Add constraints with Problem.satisfy()
            └──  Avoid common pitfalls
Problem.verify(*fragments: b.Fragment) -> None

One-shot constraint verification against the current solution.

Temporarily installs each fragment as an integrity constraint, triggers a model query to evaluate them, then removes them. A ModelWarning is raised if any constraint is violated.

Emits a UserWarning and returns without checking if the most recent solve did not produce a successful solution (i.e. termination_status is not OPTIMAL, LOCALLY_SOLVED, or SOLUTION_LIMIT).

.. note:

LP and MIP solvers return floating-point solutions that satisfy
constraints within solver tolerance (e.g. ``1e-8``), but engine
ICs check exact inequality. For continuous-variable constraints,
use a tolerant ``model.require()`` post-solve instead
(e.g. ``model.require(x <= bound + 1e-6)``).

Parameters:

  • *fragments

    (Fragment, default: ()) - One or more fragments previously passed to Problem.satisfy.
Problem.num_variables() -> b.Relationship

Number of declared decision variables. Usable in rules and ICs.

Returns:

  • Relationship - An Integer Relationship counting the declared variables.
Problem.num_constraints() -> b.Relationship

Number of declared constraints. Usable in rules and ICs.

Returns:

  • Relationship - An Integer Relationship counting the declared constraints.
Problem.num_min_objectives() -> b.Relationship

Number of minimization objectives. Usable in rules and ICs.

Returns:

  • Relationship - An Integer Relationship counting the minimization objectives.
Problem.num_max_objectives() -> b.Relationship

Number of maximization objectives. Usable in rules and ICs.

Returns:

  • Relationship - An Integer Relationship counting the maximization objectives.
Problem.termination_status() -> b.Relationship

Solver termination status (e.g. "OPTIMAL"). Usable in rules and ICs.

Returns:

  • Relationship - A String Relationship containing the termination status.
Problem.objective_value() -> b.Relationship

Objective value reported by the solver. Usable in rules and ICs.

Solvers report a single objective per solve (the primary/best solution), not per-point objectives. This Relationship reflects the solver-reported value.

Returns:

  • Relationship - A numeric Relationship containing the objective value.
Problem.solve_time_sec() -> b.Relationship

Solve time in seconds (Float Relationship). Usable in rules and ICs.

Returns:

  • Relationship - A Float Relationship containing the solve time in seconds.
Problem.num_points() -> b.Relationship

Number of solution points. Usable in rules and ICs.

Returns:

  • Relationship - An Integer Relationship counting the solution points.
Problem.solver_version() -> b.Relationship

Solver version string. Usable in rules and ICs.

Returns:

  • Relationship - A String Relationship containing the solver version.
Problem.printed_model() -> b.Relationship

Solver-provided text representation of the problem. Usable in rules and ICs.

Returns:

  • Relationship - A String Relationship containing the printed model text.
Problem.error() -> b.Relationship

Solver error message(s). Usable in rules and ICs.

A message here is not necessarily a whole-request failure: a successful solve whose secondary step failed (e.g. conflict/IIS extraction, with conflict_status == "FAILED") reports its reason here alongside a valid result. Read a failure’s scope from the accompanying status, not from the mere presence of a message.

Returns:

  • Relationship - A String Relationship containing solver error messages.
Problem.display(
part: Optional[b.Concept] = None,
*,
where: Optional[Any] = None,
limit: Optional[int] = None,
print_output: bool = True
) -> str

Print and return a human-readable summary of the problem.

With no arguments, renders a count summary and full tables of variables, objectives, and constraints.

Pass a subconcept returned by Problem.minimize, Problem.maximize, or Problem.satisfy to render only that component. Pass the umbrella concept problem.Constraint (on the Problem instance) to render every registered constraint, or problem.Objective to render every registered objective. Pass where= with a DSL predicate to scope rendering to filter-matching rows; where requires part. Variable subconcepts raise ValueError; query variable rows directly via the DSL — for example model.select(var.name, var.lower, var.upper).where(...).

Pass limit=N to cap each rendered table to its top-N rows.

Parameters:

  • part

    (Concept, default: None) - A registered constraint or objective subconcept returned by Problem.satisfy, Problem.minimize, or Problem.maximize, or the umbrella concept problem.Constraint or problem.Objective to render every registered constraint or every registered objective.
  • where

    (keyword - only, default: None) - A DSL predicate (typically referencing part’s properties, e.g. part.name == "cap_3") that further scopes the rendered rows. Requires part.
  • limit

    ((int, keyword - only), default: None) - Positive integer cap on each rendered table’s row count, with rows selected as the top-N by .name (natural-sort). For part rendering, truncated output appends "(showing N of M)" where M is the count after where= filtering; for full-problem rendering, the summary header already shows the totals so the per-section annotation is omitted.
  • print_output

    ((bool, keyword - only), default: True) - If True, print the formatted output to stdout. Set to False to receive the string without printing.

Returns:

  • str - The formatted summary.

Raises:

  • ValueError - If part is a variable subconcept or an unregistered Concept, if where is passed without part, or if limit is not a positive integer.
  • TypeError - If part is not a Concept.

Examples:

Where cap is a constraint subconcept returned by Problem.satisfy:

problem.display()
problem.display(cap)
problem.display(cap, where=cap.name == "cap_3")
problem.display(cap, limit=5)

Referenced By:

RelationalAI Documentation
├──  Build With RelationalAI
│   └──  Understand how PyRel works > Use advanced reasoning > Prescriptive reasoning > Solve a decision problem
│       ├──  Create a Problem object
│       │   └──  Inspect a Problem with display()
│       ├──  Add constraints
│       │   └──  Inspect Problem constraints
│       └──  Solve a decision problem
│           └──  Avoid common pitfalls
└──  Release Notes
    └──  Python API Release Notes
        └──  What’s New in Version 1.3.0
            └──  New Features and Enhancements
Problem.solve(
solver: str,
*,
time_limit_sec: float | None = None,
silent: bool | None = None,
solution_limit: int | None = None,
relative_gap_tolerance: float | None = None,
absolute_gap_tolerance: float | None = None,
log_to_console: bool = False,
print_only: bool = False,
print_format: str | None = None,
sensitivity: bool = False,
conflict: bool = False,
**solver_params: int | float | str | bool
) -> None

Solve the decision problem using a solver backend.

Declare decision variables first with Problem.solve_for. After solving, read back solution values via the populated properties (e.g. model.select(X.v)) or the Variable.values Property for engine-side queries, result accessors such as Problem.termination_status and Problem.objective_value, or Problem.solve_info for a Python-side snapshot.

Parameters:

  • solver

    (str) - Solver name (for example "highs", "minizinc", "ipopt").
  • time_limit_sec

    (float, default: None) - Maximum solve time in seconds. The solver service defaults to 300s if not provided.
  • silent

    (bool, default: None) - Whether to suppress solver output.
  • solution_limit

    (int, default: None) - Maximum number of solutions to return (when supported).
  • relative_gap_tolerance

    (float, default: None) - Relative optimality gap tolerance in [0, 1].
  • absolute_gap_tolerance

    (float, default: None) - Absolute optimality gap tolerance (>= 0).
  • log_to_console

    (bool, default: False) - Whether to stream solver logs to stdout while the job runs.
  • print_only

    (bool, default: False) - If True, request a text representation without solving. Results such as Problem.printed_model are still accessible afterward.
  • print_format

    (str, default: None) - Text format for the printed model. Supported formats: "moi" (MOI text), "latex", "mof" (MOI JSON), "lp", "mps", "nl" (AMPL).
  • sensitivity

    (bool, default: False) - Request post-solve sensitivity analysis — reduced costs, shadow prices, and basis statuses — exposed via Variable.reduced_cost, Constraint.shadow_price, and .basis_status. Requires an objective and is incompatible with solution_limit (sensitivity is defined for the single optimal point). Meaningful for continuous (LP/QP) solves; integer/MIP models have no meaningful duals, so the accessors come back empty (a warning is emitted). Coefficient/RHS ranging (allowable increase/decrease) is not provided. Uses wire schema version 2. Read them straight off the returned variable/constraint, the same attribute style as .name/.lower — e.g. model.select(var.name, var.reduced_cost); see ProblemVariable / ProblemConstraint for the query idiom and the dual sign convention (it depends on the objective sense and the constraint direction / active bound — not the objective sense alone).
  • conflict

    (bool, default: False) - For an infeasible model, request a conflict / irreducible infeasible subsystem (IIS) — the constraints and variable bounds that cannot all be satisfied — exposed via Constraint.in_conflict and Var.{lower,upper,integrality}_in_conflict, with the overall outcome in solve_info().conflict_status. Needs no objective (unlike sensitivity), so a pure feasibility problem can be diagnosed too. Uses wire schema version 2. Read membership with the bare predicate, e.g. where(con.in_conflict) (see ProblemConstraint).
  • **solver_params

    (int or float or str or bool, default: {}) - Raw solver-specific parameters passed through to the solver service.

Raises:

  • ValueError - If no decision variables have been declared via Problem.solve_for; if print_only=True is combined with sensitivity or conflict; if sensitivity=True is combined with a satisfaction problem (no objective) or with solution_limit; if the Problem was already solved under a different result schema (re-solving the same Problem across a plain solve and a sensitivity/conflict solve is not supported — create a new Problem); or if sensitivity / conflict is requested against a solver service too old to support wire schema version 2.
  • TypeError - If any solver-specific parameter value is not an int, float, str, or bool, or if numeric_type is passed via **solver_params (declare it via Problem(model, Float) or Problem(model, Integer) instead).
  • RuntimeError - If the solver job fails.
  • TimeoutError - If the solver job does not reach a terminal state in time.

Notes:

Passing **solver_params emits a warning because options may not be portable across solvers.

A failed solve leaves prior results intact. If a solve raises — a rejected request (ValueError), a failed job (RuntimeError), or a timeout (TimeoutError) — the Problem’s result state is unchanged: the last successful solve’s values, Problem.solve_info, and any sensitivity / conflict data all remain queryable, and solve_info() continues to describe that last successful solve. The failed attempt’s partial output is never imported. A fresh successful solve replaces the prior results.

Referenced By:

RelationalAI Documentation
├──  Build With RelationalAI
│   └──  Understand how PyRel works > Use advanced reasoning > Prescriptive reasoning
│       ├──  Choose a backend
│       │   ├──  Use Gurobi
│       │   │   └──  Example
│       │   ├──  Use Ipopt
│       │   │   └──  Example
│       │   └──  Use MiniZinc
│       │       └──  Example
│       └──  Solve a decision problem
│           ├──  Overview
│           ├──  Solve a decision problem
│           │   ├──  What happens when you solve a problem
│           │   ├──  Solve for feasibility
│           │   ├──  Solve optimally
│           │   ├──  Set a time limit
│           │   ├──  Accept a near-optimal solution
│           │   ├──  Tune solver backend behavior
│           │   ├──  Print the translated solver model
│           │   ├──  Check error details when termination status is not OPTIMAL
│           │   └──  Handle solve failures
│           └──  Work with solutions
└──  Release Notes
    └──  Python API Release Notes
        ├──  What’s New in Version 1.0.16
        │   └──  Performance and Reliability
        └──  What’s New in Version 1.4.1
            └──  Bug Fixes
Problem.variable_values(multiple: bool = False) -> b.Fragment

Return decision variable values from the first solution.

.. deprecated:

Use ``Variable.values`` instead for composable engine-side queries::
Var = problem.Variable
val = Float.ref() # or Integer.ref() for Integer-typed problems
model.select(Var.name, val).where(Var.values(0, val)) # first solution
# Multi-solution form (matches multiple=True):
sol_idx = Integer.ref()
model.select(sol_idx, Var.name, val).where(Var.values(sol_idx, val))

Use this after Problem.solve to read variable values as a fragment you can materialize with .to_df() or print with .inspect(). Without multiple, the returned fragment reflects the first solution only.

Parameters:

  • multiple

    (bool, default: False) - If True, return values for all solutions from the most recent Problem.solve call and include a 0-based sol_index column.

Returns:

  • Fragment - A fragment with columns name and value (and sol_index if multiple is True). Returns an empty DataFrame if Problem.solve has not been called.

Referenced By:

RelationalAI Documentation
├──  Build With RelationalAI
│   └──  Understand how PyRel works > Use advanced reasoning > Prescriptive reasoning > Solve a decision problem
│       ├──  Add decision variables
│       │   └──  Declare decision variables
│       └──  Work with solutions
│           ├──  Determine how to access results
│           └──  Read solver-level variable values
└──  Release Notes
    └──  Python API Release Notes
        └──  What’s New in Version 1.0.13
            └──  Upgrade Notes
Problem.load_point(point_index: int) -> None

Deprecated: switching the active solution point is no longer supported.

.. deprecated:

Populated properties and :meth:`variable_values` always reflect the
first solution (``sol_index = 0``, matching Python 0-based
indexing). ``load_point(0)`` is a no-op kept for backward
compatibility and emits a :class:`DeprecationWarning`. Any other
index raises :class:`NotImplementedError` — use
``Variable.values(sol_idx, val)`` to query a specific solution::
Var = problem.Variable
val = Float.ref() # or Integer.ref() for Integer-typed problems
model.select(Var.name, val).where(Var.values(2, val)) # 3rd solution

Parameters:

  • point_index

    (int) - Must be 0. Any other value raises NotImplementedError.

Raises:

  • ValueError - If point_index is not a non-negative integer.
  • NotImplementedError - If point_index is not 0.

Referenced By:

RelationalAI Documentation
└──  Release Notes
    └──  Python API Release Notes
        └──  What’s New in Version 1.0.13
            └──  Upgrade Notes
Problem.solve_info() -> SolveInfoData

Return solver result metadata as a cached SolveInfoData.

Fetches all result metadata in a single query. Subsequent calls return the cached snapshot until the next Problem.solve.

Returns:

  • SolveInfoData - Frozen dataclass with typed fields. Beyond the always-present termination_status / objective_value / solve_time_sec / num_points, it carries the boolean sensitivity / conflict flags echoing what the solve requested, conflict_status (after solve(conflict=True)), primal_status / dual_status (after solve(sensitivity=True)), and a raw ancillary map of any other solver metadata the service reports; see SolveInfoData. Use print(si) or si.display() for a formatted summary. If Problem.solve has not been called, all fields are None (error is ()).
RelationalAI Documentation
├──  Build With RelationalAI
│   └──  Understand how PyRel works > Use advanced reasoning > Prescriptive reasoning
│       ├──  Choose a backend
│       │   ├──  Use HiGHS
│       │   │   └──  Example
│       │   ├──  Use Gurobi
│       │   │   └──  Example
│       │   ├──  Use Ipopt
│       │   │   └──  Example
│       │   └──  Use MiniZinc
│       │       └──  Example
│       └──  Solve a decision problem
│           ├──  Overview
│           │   ├──  How PyRel represents a decision problem
│           │   └──  How solving a decision problem works
│           ├──  Create a Problem object
│           │   ├──  Choose a default numeric type
│           │   ├──  Create a Problem object
│           │   └──  Inspect a Problem with display()
│           ├──  Add decision variables
│           │   ├──  Declare decision variables
│           │   ├──  Choose variable types and bounds
│           │   └──  Inspect Problem variables
│           ├──  Add constraints
│           │   ├──  Add constraints with Problem.satisfy()
│           │   ├──  Inspect Problem constraints
│           │   └──  Avoid common pitfalls
│           ├──  Solve a decision problem
│           │   ├──  What happens when you solve a problem
│           │   ├──  Validate before you solve
│           │   ├──  Solve for feasibility
│           │   ├──  Solve optimally
│           │   ├──  Inspect solve metadata
│           │   ├──  Set a time limit
│           │   ├──  Accept a near-optimal solution
│           │   ├──  Tune solver backend behavior
│           │   ├──  Print the translated solver model
│           │   ├──  Check error details when termination status is not OPTIMAL
│           │   ├──  Handle solve failures
│           │   └──  Avoid common pitfalls
│           └──  Work with solutions
│               ├──  Determine how to access results
│               └──  Read solver-level variable values
└──  Release Notes
    └──  Python API Release Notes
        ├──  What’s New in Version 1.0.13
        │   └──  Upgrade Notes
        ├──  What’s New in Version 1.0.16
        │   └──  Performance and Reliability
        ├──  What’s New in Version 1.2.0
        │   └──  Bug Fixes
        ├──  What’s New in Version 1.3.0
        │   └──  New Features and Enhancements
        └──  What’s New in Version 1.4.1
            └──  Bug Fixes