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 Problem.variable_values or Problem.load_point.

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}")
>>> p = Problem(m, Float)
>>> p.solve_for(x, name="x", lower=0)
>>> p.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.Variable: Concept

Concept for the declared solver variables (useful for inspection via Problem.display).

Problem.Expression: Concept

Concept for objectives and constraints (useful for inspection via Problem.display).

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,
) -> b.Concept

Declare decision variables for the problem.

Call this before adding objectives or constraints. The returned concept can be passed to Problem.display to inspect the generated 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:

  • Concept - A Variable subconcept representing the declared decision variables.

Raises:

  • ValueError - If variables are already defined for this relationship, or if an argument has an invalid value (for example, an unknown 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
Problem.minimize(
expr: b.Variable | float | int | b.Fragment, name: Optional[Any | list[Any]] = None
) -> b.Concept

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:

Raises:

  • ValueError - If the objective does not reference any declared decision variables.
Problem.maximize(
expr: b.Variable | float | int | b.Fragment, name: Optional[Any | list[Any]] = None
) -> b.Concept

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:

Raises:

  • ValueError - If the objective does not reference any declared decision variables.
Problem.satisfy(expr: b.Fragment, name: Optional[Any | list[Any]] = None) -> b.Concept

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

Use this to turn a require-clause fragment into solver constraints. The returned concept can be passed to Problem.display to inspect.

To check whether the solver’s solution satisfies this constraint after Problem.solve, call Problem.verify — it temporarily installs 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).

Returns:

  • Concept - An Expression subconcept representing the added constraints.

Raises:

  • TypeError - If expr is not a fragment.
  • ValueError - If the fragment has no require clause, or if it includes select/define clauses.

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:

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 from the first solution. Usable in rules and ICs.

Hardwired to solution index 1 (the primary/best solution). Problem.load_point changes which solution’s variable values are active, but does not change the reported objective — solvers report a single objective for the overall solve, not per-point objectives.

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.

Returns:

  • Relationship - A String Relationship containing solver error messages.
Problem.display(part: Optional[b.Concept] = None) -> str

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

With no arguments, this shows all declared variables, objectives, and constraints. Pass a subconcept returned by Problem.solve_for, Problem.minimize, Problem.maximize, or Problem.satisfy to display only that part.

Parameters:

  • part

    (Concept, default: None) - Specific variable/objective/constraint subconcept to display.

Returns:

  • str - The formatted summary (also printed).

Raises:

Examples:

Display the full problem:

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

Display a single part (a subconcept returned by solve_for/minimize/satisfy):

>>> p.display(x_vars)

Notes:

This method queries the model.

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
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,
**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, inspect results via Problem.variable_values and result accessors such as Problem.termination_status and Problem.objective_value, or use 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).
  • **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.
  • TypeError - If any solver-specific parameter value is not an int, float, str, or bool.
  • 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.

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
Problem.variable_values(multiple: bool = False) -> b.Fragment

Return decision variable values from the active solution.

Use this after Problem.solve (and optionally Problem.load_point) to read variable values as a fragment you can materialize with .to_df() or print with .inspect().

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
Problem.load_point(point_index: int) -> None

Make a specific solution point the active solution.

After calling this, Problem.variable_values and populated properties reflect the selected solution.

Parameters:

  • point_index

    (int) - 0-based solution index (0 selects the first solution).

Raises:

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

Notes:

No range validation is performed (it would require an engine query). If point_index exceeds available solutions, Problem.variable_values returns an empty DataFrame. Use solve_info().num_points to check bounds beforehand.

Each call records a new selection via model.define(...). The next query may be slower because the model needs to re-evaluate.

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. 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