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.
Parameters
Section titled “Parameters”
(modelModel) - TheModelto attach solver constructs (variables, objectives, constraints, and results) to.
(numeric_typeConcept) - Numeric type used for bounds, solution values, and numeric literals. Usesemantics.frontend.core.Floatfor HiGHS/Gurobi/Ipopt (even for MIPs), andsemantics.frontend.core.Integerfor MiniZinc.
Examples
Section titled “Examples”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.
Attributes
Section titled “Attributes”.numeric_type
Section titled “.numeric_type”Problem.numeric_type: Conceptsemantics.frontend.core.Float or semantics.frontend.core.Integer.
.variables
Section titled “.variables”Problem.variables: list[ProblemVariable]Variable components registered via Problem.solve_for.
.objectives
Section titled “.objectives”Problem.objectives: list[ProblemObjective]Objective components registered via Problem.minimize / Problem.maximize.
.constraints
Section titled “.constraints”Problem.constraints: list[ProblemConstraint]Constraint components registered via Problem.satisfy.
.Variable
Section titled “.Variable”Problem.Variable: ConceptEngine-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.
.Objective
Section titled “.Objective”Problem.Objective: ConceptEngine-side aggregate Concept covering all objectives. Each
ProblemObjective extends this Concept; use it to query
names, types, and (in future) duals or printed expressions.
.Constraint
Section titled “.Constraint”Problem.Constraint: ConceptEngine-side aggregate Concept covering all constraints. Each
ProblemConstraint extends this Concept; use it to query
names, types, and (in future) duals or printed expressions.
Methods
Section titled “Methods”.solve_for()
Section titled “.solve_for()”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:
(exprRelationship or Chain or Expression) - Expression describing the variable(s) to create (for example, a scalar relationship likexor an indexed property likeItem.cost).
(wherelist[Any], default:None) - Optional conditions restricting which variable instances are created.
(populatebool, default:True) - If True (default), write solved values back to the original relationship/property afterProblem.solve. Set to False when you create multipleProbleminstances that solve for the same relationship on the same model.
(nameAny 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]).
(typestr, default:None) - Variable type:"cont"(default forFloat),"int"(default forInteger), or"bin"(binary 0/1).
(lowerVariable or float or int or Decimal, default:None) - Lower/upper bounds and an optional initial value hint.
(upperVariable or float or int or Decimal, default:None) - Lower/upper bounds and an optional initial value hint.
(startVariable or float or int or Decimal, default:None) - Lower/upper bounds and an optional initial value hint.
Returns:
-
ProblemVariable- The variable subconcept. Annotate viamodel.define(var.lower(0)), query values after solve viamodel.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
:namefrom 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.weekvar = 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.jvar = 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 unknowntype), 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@propertydescriptors (dsl_expr,concept_name,property_name,var_type,var_where,populate), andConcept.RESERVED_NAMESmethods (ref,new,alias,where,select,define,require, etc.). SeeProblem._reserved_variable_field_namesfor the authoritative list — the error message lists the full set. Rename the conflicting field in the relationship’s format string.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
.minimize()
Section titled “.minimize()”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:
(exprVariable or float or int or Fragment) - Objective expression to minimize.
(nameAny 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.
.maximize()
Section titled “.maximize()”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:
(exprVariable or float or int or Fragment) - Objective expression to maximize.
(nameAny 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.
.satisfy()
Section titled “.satisfy()”Problem.satisfy( expr: b.Fragment, name: Optional[Any | list[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 satisfyconstraints within solver tolerance (e.g. ``1e-8``), but engineICs check exact inequality. For continuous-variable constraints,use a tolerant ``model.require()`` post-solve instead(e.g. ``model.require(x <= bound + 1e-6)``).Parameters:
(exprFragment) - A fragment created byModel.require(optionally scoped withModel.where).
(nameAny or list[Any], default:None) - Optional constraint name (string for scalar, or a list pattern for indexed constraints).
Returns:
ProblemConstraint- The constraint subconcept (IS a Concept).
Raises:
TypeError- Ifexpris not a fragment.ValueError- If the fragment has no require clause, or if it includes select/define clauses.
.verify()
Section titled “.verify()”Problem.verify(*fragments: b.Fragment) -> NoneOne-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 satisfyconstraints within solver tolerance (e.g. ``1e-8``), but engineICs check exact inequality. For continuous-variable constraints,use a tolerant ``model.require()`` post-solve instead(e.g. ``model.require(x <= bound + 1e-6)``).Parameters:
(*fragmentsFragment, default:()) - One or more fragments previously passed toProblem.satisfy.
.num_variables()
Section titled “.num_variables()”Problem.num_variables() -> b.RelationshipNumber of declared decision variables. Usable in rules and ICs.
Returns:
Relationship- An Integer Relationship counting the declared variables.
.num_constraints()
Section titled “.num_constraints()”Problem.num_constraints() -> b.RelationshipNumber of declared constraints. Usable in rules and ICs.
Returns:
Relationship- An Integer Relationship counting the declared constraints.
.num_min_objectives()
Section titled “.num_min_objectives()”Problem.num_min_objectives() -> b.RelationshipNumber of minimization objectives. Usable in rules and ICs.
Returns:
Relationship- An Integer Relationship counting the minimization objectives.
.num_max_objectives()
Section titled “.num_max_objectives()”Problem.num_max_objectives() -> b.RelationshipNumber of maximization objectives. Usable in rules and ICs.
Returns:
Relationship- An Integer Relationship counting the maximization objectives.
.termination_status()
Section titled “.termination_status()”Problem.termination_status() -> b.RelationshipSolver termination status (e.g. "OPTIMAL"). Usable in rules and ICs.
Returns:
Relationship- A String Relationship containing the termination status.
.objective_value()
Section titled “.objective_value()”Problem.objective_value() -> b.RelationshipObjective 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.
.solve_time_sec()
Section titled “.solve_time_sec()”Problem.solve_time_sec() -> b.RelationshipSolve time in seconds (Float Relationship). Usable in rules and ICs.
Returns:
Relationship- A Float Relationship containing the solve time in seconds.
.num_points()
Section titled “.num_points()”Problem.num_points() -> b.RelationshipNumber of solution points. Usable in rules and ICs.
Returns:
Relationship- An Integer Relationship counting the solution points.
.solver_version()
Section titled “.solver_version()”Problem.solver_version() -> b.RelationshipSolver version string. Usable in rules and ICs.
Returns:
Relationship- A String Relationship containing the solver version.
.printed_model()
Section titled “.printed_model()”Problem.printed_model() -> b.RelationshipSolver-provided text representation of the problem. Usable in rules and ICs.
Returns:
Relationship- A String Relationship containing the printed model text.
.error()
Section titled “.error()”Problem.error() -> b.RelationshipSolver error message(s). Usable in rules and ICs.
Returns:
Relationship- A String Relationship containing solver error messages.
.display()
Section titled “.display()”Problem.display(part: Optional[b.Concept] = None, *, print_output: bool = True) -> strPrint and return a human-readable summary of the problem.
With no arguments, this shows a header with the numeric type, a bullet-list summary of variable/objective/constraint counts (zero categories are omitted), and tables of all declared variables, objectives, and constraints. Nothing is truncated — long expressions and large tables are emitted in full.
Pass a ProblemVariable, ProblemObjective, or
ProblemConstraint returned by Problem.solve_for,
Problem.minimize, Problem.maximize, or Problem.satisfy to display only
that component.
Parameters:
(partProblemVariable or ProblemObjective or ProblemConstraint, default:None) - Specific component to display. Pass the value returned by the builder methods directly.
(print_output(bool, keyword - only), default:True) - IfTrue, print the formatted output to stdout as a side effect. Set toFalseto receive the string without printing.
Returns:
str- The formatted summary.
Raises:
ValueError- Ifpartis not a subconcept returned byProblem.solve_for,Problem.minimize,Problem.maximize, orProblem.satisfy.
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}")>>> problem = Problem(m, Float)>>> x_vars = problem.solve_for(x, name="x", lower=0)>>> problem.minimize(x)>>> problem.display()Display a single component (returned by solve_for/minimize/satisfy):
>>> problem.display(x_vars)Capture output without printing:
>>> text = problem.display(print_output=False)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 withdisplay()├── Add constraints │ └── InspectProblemconstraints └── Solve a decision problem └── Avoid common pitfalls
.solve()
Section titled “.solve()”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) -> NoneSolve 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:
(solverstr) - Solver name (for example"highs","minizinc","ipopt").
(time_limit_secfloat, default:None) - Maximum solve time in seconds. The solver service defaults to 300s if not provided.
(silentbool, default:None) - Whether to suppress solver output.
(solution_limitint, default:None) - Maximum number of solutions to return (when supported).
(relative_gap_tolerancefloat, default:None) - Relative optimality gap tolerance in[0, 1].
(absolute_gap_tolerancefloat, default:None) - Absolute optimality gap tolerance (>= 0).
(log_to_consolebool, default:False) - Whether to stream solver logs to stdout while the job runs.
(print_onlybool, default:False) - If True, request a text representation without solving. Results such asProblem.printed_modelare still accessible afterward.
(print_formatstr, default:None) - Text format for the printed model. Supported formats:"moi"(MOI text),"latex","mof"(MOI JSON),"lp","mps","nl"(AMPL).
(**solver_paramsint 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 viaProblem.solve_for.TypeError- If any solver-specific parameter value is not anint,float,str, orbool.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.variable_values()
Section titled “.variable_values()”Problem.variable_values(multiple: bool = False) -> b.FragmentReturn 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:
(multiplebool, default:False) - If True, return values for all solutions from the most recentProblem.solvecall and include a 0-basedsol_indexcolumn.
Returns:
Fragment- A fragment with columnsnameandvalue(andsol_indexif multiple is True). Returns an empty DataFrame ifProblem.solvehas 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
.load_point()
Section titled “.load_point()”Problem.load_point(point_index: int) -> NoneDeprecated: switching the active solution point is no longer supported.
.. deprecated:
Populated properties and :meth:`variable_values` always reflect thefirst solution (``sol_index = 0``, matching Python 0-basedindexing). ``load_point(0)`` is a no-op kept for backwardcompatibility and emits a :class:`DeprecationWarning`. Any otherindex 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 solutionParameters:
(point_indexint) - Must be0. Any other value raisesNotImplementedError.
Raises:
ValueError- If point_index is not a non-negative integer.NotImplementedError- If point_index is not0.
.solve_info()
Section titled “.solve_info()”Problem.solve_info() -> SolveInfoDataReturn 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. Useprint(si)orsi.display()for a formatted summary. IfProblem.solvehas not been called, all fields are None (erroris()).
Referenced By
Section titled “Referenced By”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 withdisplay()├── Add decision variables │ ├── Declare decision variables │ ├── Choose variable types and bounds │ └── InspectProblemvariables ├── Add constraints │ ├── Add constraints withProblem.satisfy()│ ├── InspectProblemconstraints │ └── 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 notOPTIMAL│ ├── Handle solve failures │ └── Avoid common pitfalls └── Work with solutions ├── Determine how to access results └── Read solver-level variable values