Skip to content

This feature is currently in Preview.

The Gurobi Solver Backend

Gurobi is a state-of-the-art commercial solver for mathematical optimization. Use it to solve complex linear, mixed-integer, and quadratic programs using RelationalAI’s SolverModel API.

Gurobi supports the following problem types:

Problem TypeSupported
Linear Programs (LP)
Mixed-Integer Linear Programs (MILP)
Quadratic Programs (QP)
Quadratically Constrained Programs (QCP)
Nonlinear Programs (NLP)
Constraint Programming (CP)
Discrete Variables
Continuous Variables

Gurobi is a commercial solver that requires a valid license. You must install and configure your Gurobi license before using it with RelationalAI.

DetailInfo
Version12.0.1
License TypeCommercial (License required)

See the Gurobi docs for details on obtaining a license.

To use Gurobi with RelationalAI, you must store your Gurobi license key in Snowflake and configure network access for the RAI Native App to authenticate the license.

Follow these steps to set up Gurobi in RelationalAI:

  1. Get a Gurobi license key.

    Your license key has the following form:

    WLSACCESSID=...
    WLSSECRET=...
    LICENSEID=...

    Visit the Gurobi website to obtain a license, if necessary.

  2. Store your license key in Snowflake.

    First, choose the database and schema to store your Gurobi license key, or create a new database and schema if needed:

    CREATE DATABASE IF NOT EXISTS solvers;
    CREATE SCHEMA IF NOT EXISTS solvers.secrets;

    Then create a Snowflake secret to store your Gurobi license key:

    CREATE SECRET IF NOT EXISTS solvers.secrets.gurobi_license
    TYPE=generic_string
    -- Replace <MY_GUROBI_LICENSE_KEY> with your actual Gurobi license key.
    SECRET_STRING='<MY_GUROBI_LICENSE_KEY>'
    GRANT USAGE ON DATABASE solvers TO APPLICATION RELATIONALAI;
    GRANT USAGE ON SCHEMA solvers.secrets TO APPLICATION RELATIONALAI;
    GRANT READ ON SECRET solvers.secrets.gurobi_license TO APPLICATION RELATIONALAI;
  3. Allow RAI to authenticate your license key.

    The RAI Native App needs to access the Gurobi license server to authenticate your license key, so you must create a network rule and an external access integration in Snowflake.

    First, choose the database and schema to store the network rule and integration. Here, we create a schema named networking in the solvers database created in the previous step:

    CREATE SCHEMA IF NOT EXISTS solvers.networking;

    Then create an the network rule and external access integration:

    CREATE OR REPLACE NETWORK RULE solvers.networking.gurobi_network_rule
    MODE = EGRESS
    TYPE = HOST_PORT
    VALUE_LIST = ('token.gurobi.com:443');
    CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION gurobi_integration
    ALLOWED_NETWORK_RULES = (solvers.networking.gurobi_network_rule)
    ENABLED = true;
    GRANT USAGE ON SCHEMA solvers.networking TO APPLICATION RELATIONALAI;
    GRANT USAGE ON INTEGRATION gurobi_integration TO APPLICATION RELATIONALAI;

To use Gurobi in RelationalAI:

  1. Enable the Gurobi solver backend.

    Add the following to the bottom of your raiconfig.toml file:

    raiconfig.toml
    active_profile = "default"
    [profile.default]
    9 collapsed lines
    platform = "snowflake"
    user = "c.janeway@starfleet.org"
    password = "C0ffee>PrimeDirective"
    account = "ncc74656-voyager"
    role = "CAPTAIN"
    warehouse = "MAIN"
    rai_app_name = "relationalai"
    engine = "catherine_janeway"
    engine_size = "HIGHMEM_X64_S"
    [experimental.solvers]
    gurobi.enabled=true
    gurobi.license_secret_name="<MY_GUROBI_SECRET_NAME>" # E.g., "solvers.secrets.gurobi_license"
    gurobi.external_access_integration="<MY_GUROBI_INTEGRATION_NAME>" # E.g., "gurobi_integration"

    See Required Setup for details on setting up the license secret and external access integration.

  2. Create a Solver object with "gurobi".

    In your Python code, pass the string "gurobi" to the Solver constructor to create a Gurobi solver instance:

    regional_water_allocation.py
    import relationalai as rai
    from relationalai.std import alias
    from relationalai.experimental import solvers
    # Create a RAI model.
    model = rai.Model("RegionalWaterAllocation")
    67 collapsed lines
    # Declare water distribution stage type.
    WaterSource = model.Type("WaterSource")
    Region = model.Type("Region")
    Consumer = model.Type("Consumer")
    Consumer.region.declare() # Region where the consumer is located.
    Consumer.demand.declare() # Water demand of the consumer.
    # Declare Pipeline type to connect stages.
    Pipeline = model.Type("Pipeline")
    Pipeline.source.declare()
    Pipeline.destination.declare()
    Pipeline.capacity.declare()
    # Define water sources.
    with model.rule(dynamic=True):
    WaterSource.add(name="Reservoir")
    # Define regions.
    for region in [
    {"id": 1, "name": "North"},
    {"id": 2, "name": "South"},
    {"id": 3, "name": "East"},
    {"id": 4, "name": "West"},
    ]:
    with model.rule():
    Region.add(id=region["id"]).set(name=region["name"])
    # Define consumers.
    for consumer in [
    {"id": 1, "region_id": 1, "name": "Residential", "demand": 150.0},
    {"id": 2, "region_id": 1, "name": "Industrial", "demand": 60.0},
    {"id": 3, "region_id": 1, "name": "Farms", "demand": 40.0},
    {"id": 4, "region_id": 2, "name": "Residential", "demand": 80.0},
    {"id": 5, "region_id": 2, "name": "Industrial", "demand": 140.0},
    {"id": 6, "region_id": 2, "name": "Farms", "demand": 50.0},
    {"id": 7, "region_id": 3, "name": "Residential", "demand": 90.0},
    {"id": 8, "region_id": 3, "name": "Industrial", "demand": 180.0},
    {"id": 9, "region_id": 4, "name": "Residential", "demand": 40.0},
    {"id": 10, "region_id": 4, "name": "Industrial", "demand": 30.0},
    {"id": 11, "region_id": 4, "name": "Farms", "demand": 200.0},
    ]:
    with model.rule():
    region = Region(id=consumer["region_id"])
    Consumer.add(id=consumer["id"]).set(
    name=consumer["name"],
    region=region,
    demand=consumer["demand"]
    )
    # Define pipelines from the reservoir to each region.
    for region_id, capacity in [(1, 260.0), (2, 300.0), (3, 200.0), (4, 220.0)]:
    with model.rule():
    source = WaterSource(name="Reservoir")
    region = Region(id=region_id)
    Pipeline.add(source=source, destination=region).set(capacity=capacity)
    # Define pipelines between consumers and their regions.
    for consumer_id, capacity in [
    (1, 150.0), (2, 60.0), (3, 40.0), # North
    (4, 80.0), (5, 140.0), (6, 50.0), # South
    (7, 90.0), (8, 180.0), # East
    (9, 40.0), (10, 30.0), (11, 200.0) # West
    ]:
    with model.rule():
    consumer = Consumer(id=consumer_id)
    region = consumer.region
    Pipeline.add(source=region, destination=consumer).set(capacity=capacity)
    # Create a solver model.
    solver_model = solvers.SolverModel(model)
    48 collapsed lines
    # Specify variables, constraints, and objective.
    # Define continuous variables for each pipeline to represent it's flow.
    with model.rule():
    pipe = Pipeline()
    solver_model.variable(
    pipe,
    name_args=["pipe", pipe.source.name, pipe.destination.name],
    lower=0,
    upper=pipe.capacity,
    )
    # Define a constraint to ensure flow conservation in each region.
    # NOTE: `solvers.operators()` overrides infix operators like `==` to build
    # solver expressions instead of filter expressions.
    with solvers.operators():
    region = Region()
    flow_in = solvers.sum(Pipeline(destination=region), per=[region])
    flow_out = solvers.sum(Pipeline(source=region), per=[region])
    solver_model.constraint(
    flow_in == flow_out,
    name_args=["preserve_flow", region.name]
    )
    # Define a constraint satisfy at least half of high-demand consumers' demand.
    with model.rule():
    consumer = Consumer()
    consumer.demand >= 100.0 # Filter for high-demand consumers.
    pipe = Pipeline(source=consumer.region, destination=consumer)
    with solvers.operators():
    solver_model.constraint(
    pipe >= .5 * consumer.demand,
    name_args=["satisfy_consumer", consumer.id]
    )
    # Define a constraint to limit the total flow from the reservoir.
    with solvers.operators():
    total_flow = solvers.sum(Pipeline(source=WaterSource(name="Reservoir")))
    solver_model.constraint(
    total_flow <= 1000.0, # Max flow from the reservoir
    name_args=["max_reservoir_flow"]
    )
    # Define the objective to minimize unmet demand.
    with solvers.operators():
    consumer = Consumer()
    pipe = Pipeline(source=consumer.region, destination=consumer)
    unmet_demand_per_consumer = consumer.demand - solvers.sum(pipe, per=[consumer])
    solver_model.min_objective(solvers.sum(unmet_demand_per_consumer))
    # Create a HiGHS Solver instance.
    solver = solvers.Solver("gurobi")
    # Solve the model.
    solver_model.solve(solver)
    # View the solution's objective value.
    with model.query() as select:
    response = select(solver_model.objective_value)
    print(f"\n\nUnmet demand (optimized): {response.results.iloc[0, 0]}")
    # View the optimized flow for each pipeline.
    with model.query() as select:
    pipe = Pipeline()
    response = select(
    alias(pipe.source.name, "source_name"),
    alias(pipe.destination.name, "destination_name"),
    solver_model.value(pipe)
    )
    print("\n\nOptimized pipeline flows:")
    print(response.results)

In addition to the standard solver options, Gurobi supports other options, like Method or MemLimit, that can be set when solving a prescriptive model using the SolverModel API.

To set these options, pass them as a keyword argument to a SolverModel object’s .solve() method while preserving the PascalCase naming convention used by Gurobi:

regional_water_allocation.py
import relationalai as rai
from relationalai.std import alias
from relationalai.experimental import solvers
# Create a RAI model.
model = rai.Model("RegionalWaterAllocation")
119 collapsed lines
# Declare water distribution stage type.
WaterSource = model.Type("WaterSource")
Region = model.Type("Region")
Consumer = model.Type("Consumer")
Consumer.region.declare() # Region where the consumer is located.
Consumer.demand.declare() # Water demand of the consumer.
# Declare Pipeline type to connect stages.
Pipeline = model.Type("Pipeline")
Pipeline.source.declare()
Pipeline.destination.declare()
Pipeline.capacity.declare()
# Define water sources.
with model.rule(dynamic=True):
WaterSource.add(name="Reservoir")
# Define regions.
for region in [
{"id": 1, "name": "North"},
{"id": 2, "name": "South"},
{"id": 3, "name": "East"},
{"id": 4, "name": "West"},
]:
with model.rule():
Region.add(id=region["id"]).set(name=region["name"])
# Define consumers.
for consumer in [
{"id": 1, "region_id": 1, "name": "Residential", "demand": 150.0},
{"id": 2, "region_id": 1, "name": "Industrial", "demand": 60.0},
{"id": 3, "region_id": 1, "name": "Farms", "demand": 40.0},
{"id": 4, "region_id": 2, "name": "Residential", "demand": 80.0},
{"id": 5, "region_id": 2, "name": "Industrial", "demand": 140.0},
{"id": 6, "region_id": 2, "name": "Farms", "demand": 50.0},
{"id": 7, "region_id": 3, "name": "Residential", "demand": 90.0},
{"id": 8, "region_id": 3, "name": "Industrial", "demand": 180.0},
{"id": 9, "region_id": 4, "name": "Residential", "demand": 40.0},
{"id": 10, "region_id": 4, "name": "Industrial", "demand": 30.0},
{"id": 11, "region_id": 4, "name": "Farms", "demand": 200.0},
]:
with model.rule():
region = Region(id=consumer["region_id"])
Consumer.add(id=consumer["id"]).set(
name=consumer["name"],
region=region,
demand=consumer["demand"]
)
# Define pipelines from the reservoir to each region.
for region_id, capacity in [(1, 260.0), (2, 300.0), (3, 200.0), (4, 220.0)]:
with model.rule():
source = WaterSource(name="Reservoir")
region = Region(id=region_id)
Pipeline.add(source=source, destination=region).set(capacity=capacity)
# Define pipelines between consumers and their regions.
for consumer_id, capacity in [
(1, 150.0), (2, 60.0), (3, 40.0), # North
(4, 80.0), (5, 140.0), (6, 50.0), # South
(7, 90.0), (8, 180.0), # East
(9, 40.0), (10, 30.0), (11, 200.0) # West
]:
with model.rule():
consumer = Consumer(id=consumer_id)
region = consumer.region
Pipeline.add(source=region, destination=consumer).set(capacity=capacity)
# Create a solver model.
solver_model = solvers.SolverModel(model)
# Specify variables, constraints, and objective.
# Define continuous variables for each pipeline to represent it's flow.
with model.rule():
pipe = Pipeline()
solver_model.variable(
pipe,
name_args=["pipe", pipe.source.name, pipe.destination.name],
lower=0,
upper=pipe.capacity,
)
# Define a constraint to ensure flow conservation in each region.
# NOTE: `solvers.operators()` overrides infix operators like `==` to build
# solver expressions instead of filter expressions.
with solvers.operators():
region = Region()
flow_in = solvers.sum(Pipeline(destination=region), per=[region])
flow_out = solvers.sum(Pipeline(source=region), per=[region])
solver_model.constraint(
flow_in == flow_out,
name_args=["preserve_flow", region.name]
)
# Define a constraint satisfy at least half of high-demand consumers' demand.
with model.rule():
consumer = Consumer()
consumer.demand >= 100.0 # Filter for high-demand consumers.
pipe = Pipeline(source=consumer.region, destination=consumer)
with solvers.operators():
solver_model.constraint(
pipe >= .5 * consumer.demand,
name_args=["satisfy_consumer", consumer.id]
)
# Define a constraint to limit the total flow from the reservoir.
with solvers.operators():
total_flow = solvers.sum(Pipeline(source=WaterSource(name="Reservoir")))
solver_model.constraint(
total_flow <= 1000.0, # Max flow from the reservoir
name_args=["max_reservoir_flow"]
)
# Define the objective to minimize unmet demand.
with solvers.operators():
consumer = Consumer()
pipe = Pipeline(source=consumer.region, destination=consumer)
unmet_demand_per_consumer = consumer.demand - solvers.sum(pipe, per=[consumer])
solver_model.min_objective(solvers.sum(unmet_demand_per_consumer))
# Create a Gurobi Solver instance.
solver = solvers.Solver("gurobi")
# Solve the model.
solver_model.solve(solver, Method=1, MemLimit=1024)
  • Some RAI operators, like solvers.if_then_else(), aren’t supported.
  • Advanced Gurobi features, like callbacks, aren’t supported.
  • Although Gurobi supports multi-objective optimization, this capability is not yet exposed by RelationalAI.