Create a Problem object
Turn a Model into a solver-backed decision problem by creating a Problem.
This guide covers choosing a default numeric type, creating the Problem, and using display() to confirm it starts empty before you add variables, requirements, or an objective.
What a Problem object is
Section titled “What a Problem object is”A Problem is a container for the variables, requirements, and objective that make up one decision problem.
It is associated with a Model, and it compiles your model definitions into something a solver backend can execute.
Choose a default numeric type
Section titled “Choose a default numeric type”When you create a Problem, you choose a default numeric type that affects how the problem is interpreted by solvers and which solver families are compatible.
Use this table to choose the default numeric type passed to Problem(model, ...):
| What to use | When to use it |
|---|---|
Problem(m, Float) | You want linear, mixed-integer, or nonlinear optimization with solvers like "highs", "gurobi", or "ipopt". |
Problem(m, Integer) | You want constraint-programming style solves with "minizinc". |
- The numeric type you pick here is a default.
- You still control whether a specific decision variable is continuous, integer, or binary when you declare it with
Problem.solve_for().
Create a Problem object
Section titled “Create a Problem object”Create an instance of the Problem class to create a decision problem associated with a model:
from relationalai.semantics import Float, Integer, Modelfrom relationalai.semantics.reasoners.prescriptive import Problem
m = Model("ShiftAssignment")
# Declare the model's schemaWorker = m.Concept("Worker", identify_by={"id": Integer})Shift = m.Concept("Shift", identify_by={"id": Integer})
Worker.available_shifts = m.Relationship(f"{Worker} is available for {Shift}")Worker.cost_for_shift = m.Relationship(f"{Worker} working {Shift} has cost {Float:cost}")Shift.required_workers = m.Property(f"{Shift} requires {Integer:n} workers")
# Define base facts.41 collapsed lines
workers = m.data([ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}, {"id": 3, "name": "Chen"},])shifts = m.data([ {"id": 10, "name": "Morning"}, {"id": 20, "name": "Evening"},])availability = m.data([ {"worker_id": 1, "shift_id": 10}, {"worker_id": 2, "shift_id": 10}, {"worker_id": 2, "shift_id": 20}, {"worker_id": 3, "shift_id": 20},])costs = m.data([ {"worker_id": 1, "shift_id": 10, "cost": 9.0}, {"worker_id": 2, "shift_id": 10, "cost": 10.0}, {"worker_id": 2, "shift_id": 20, "cost": 8.0}, {"worker_id": 3, "shift_id": 20, "cost": 11.0},])required = m.data([ {"shift_id": 10, "required_workers": 1}, {"shift_id": 20, "required_workers": 1},])
m.define( Worker.new(workers.to_schema()), Shift.new(shifts.to_schema()),)
worker = Worker.filter_by(id=availability.worker_id)shift = Shift.filter_by(id=availability.shift_id)m.define(worker.available_shifts(shift))
worker = Worker.filter_by(id=costs.worker_id)shift = Shift.filter_by(id=costs.shift_id)m.define(worker.cost_for_shift(shift, costs.cost))
shift = Shift.filter_by(id=required.shift_id)m.define(shift.required_workers(required.required_workers))
# Create a Problem object for solver-backed decision problemsp = Problem(m, Float)In this example:
Model("ShiftAssignment")creates a model to hold all schema, data, and reasoning artifacts for this problem.WorkerandShiftare the two concepts in the model, and both are identified by an integeridproperty.- Worker-Shift availability, costs, and required coverage are represented as relationships and properties.
- Base facts are loaded from inline data to populate the inputs to the decision problem.
Problem(m, Float)creates a decision problem associated with the modelm.- At this point you have created a
Problem, but you have not declared any decision variables, requirements, or objectives.
Inspect a Problem with display()
Section titled “Inspect a Problem with display()”Use Problem.display() to see an overview of the current state of the problem.
This is a good check to run after each major formulation step:
from relationalai.semantics import Float, Integer, Modelfrom relationalai.semantics.reasoners.prescriptive import Problem
m = Model("ShiftAssignment")
# Declare the model's schema49 collapsed lines
Worker = m.Concept("Worker", identify_by={"id": Integer})Shift = m.Concept("Shift", identify_by={"id": Integer})
Worker.available_shifts = m.Relationship(f"{Worker} is available for {Shift}")Worker.cost_for_shift = m.Relationship(f"{Worker} working {Shift} has cost {Float:cost}")Shift.required_workers = m.Property(f"{Shift} requires {Integer:n} workers")
# Define base facts.workers = m.data([ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}, {"id": 3, "name": "Chen"},])shifts = m.data([ {"id": 10, "name": "Morning"}, {"id": 20, "name": "Evening"},])availability = m.data([ {"worker_id": 1, "shift_id": 10}, {"worker_id": 2, "shift_id": 10}, {"worker_id": 2, "shift_id": 20}, {"worker_id": 3, "shift_id": 20},])costs = m.data([ {"worker_id": 1, "shift_id": 10, "cost": 9.0}, {"worker_id": 2, "shift_id": 10, "cost": 10.0}, {"worker_id": 2, "shift_id": 20, "cost": 8.0}, {"worker_id": 3, "shift_id": 20, "cost": 11.0},])required = m.data([ {"shift_id": 10, "required_workers": 1}, {"shift_id": 20, "required_workers": 1},])
m.define( Worker.new(workers.to_schema()), Shift.new(shifts.to_schema()),)
worker = Worker.filter_by(id=availability.worker_id)shift = Shift.filter_by(id=availability.shift_id)m.define(worker.available_shifts(shift))
worker = Worker.filter_by(id=costs.worker_id)shift = Shift.filter_by(id=costs.shift_id)m.define(worker.cost_for_shift(shift, costs.cost))
shift = Shift.filter_by(id=required.shift_id)m.define(shift.required_workers(required.required_workers))
# Create a Problem object for solver-backed decision problemsp = Problem(m, Float)
p.display()- When you called
.display()immediately after creating theProblem, you will see an “empty” problem with no decision variables, requirements, or objectives. - This is expected, since you have not yet declared any of those things.