Note
You can download this example as a Jupyter notebook or start it in interactive mode.
Solve on OETC (OET Cloud)#
This example demonstrates how to use linopy with OETC (OET Cloud) for cloud-based optimization solving. OETC is a cloud platform that provides scalable computing resources for optimization problems.
What you need to run this example:#
A working installation of the required packages:
pip install google-cloud-storage requests
An OETC account with valid credentials (email and password)
Access to OETC authentication and orchestrator servers
How OETC Cloud Solving Works#
The OETC integration follows this workflow:
Model Creation: Define your optimization model locally using linopy
Authentication: Sign in to the OETC platform using your credentials
File Upload: Compress and upload your model to Google Cloud Storage
Job Submission: Submit a compute job to the OETC orchestrator
Job Monitoring: Wait for job completion with automatic status polling
Solution Download: Download and decompress the solved model
Local Integration: Load the solution back into your local model
All of these steps are handled automatically by linopy’s OetcHandler.
Note: This notebook requires Google Cloud credentials and access to the OETC platform. It is not executed during the documentation build, so no cell outputs are shown. To run it yourself, install the
linopy[oetc]extra and configure your credentials.
Create a Model#
First, let’s create an optimization model that we want to solve on OETC:
[ ]:
from numpy import arange
from xarray import DataArray
from linopy import Model
# Create a medium-sized optimization problem
N = 50
m = Model()
# Define decision variables with coordinates
coords = [arange(N), arange(N)]
x = m.add_variables(coords=coords, name="x", lower=0)
y = m.add_variables(coords=coords, name="y", lower=0)
# Add constraints
m.add_constraints(x - y >= DataArray(arange(N)), name="constraint1")
m.add_constraints(x + y >= DataArray(arange(N) * 0.5), name="constraint2")
m.add_constraints(x <= DataArray(arange(N) + 10), name="upper_bounds")
# Set objective function
m.add_objective((2 * x + y).sum())
print(
f"Model created with {len(m.variables)} variable groups and {len(m.constraints)} constraint groups"
)
m
Configure OETC Settings#
There are two ways to configure OETC settings:
Manual construction — build
OetcCredentialsandOetcSettingsexplicitly``OetcSettings.from_env()`` — resolve credentials and options from environment variables
Option 1: Manual Construction#
[ ]:
# Configure your OETC credentials
# IMPORTANT: Never hardcode credentials in production code!
# Use environment variables or secure credential management
import os
from linopy.remote.oetc import (
ComputeProvider,
OetcCredentials,
OetcHandler,
OetcSettings,
)
credentials = OetcCredentials(
email=os.getenv("OETC_EMAIL", "your-email@example.com"),
password=os.getenv("OETC_PASSWORD", "your-password"),
)
# Configure OETC settings
settings = OetcSettings(
credentials=credentials,
name="linopy-example-job",
authentication_server_url="https://auth.oetcloud.com", # Replace with actual URL
orchestrator_server_url="https://orchestrator.oetcloud.com", # Replace with actual URL
compute_provider=ComputeProvider.GCP,
cpu_cores=4, # Number of CPU cores to allocate
disk_space_gb=20, # Disk space in GB
delete_worker_on_error=False, # Keep worker for debugging if job fails
)
print("OETC settings configured successfully")
print(f"Solver: {settings.solver}")
print(f"CPU cores: {settings.cpu_cores}")
print(f"Disk space: {settings.disk_space_gb} GB")
Option 2: Create Settings from Environment Variables#
OetcSettings.from_env() reads configuration from environment variables, with optional keyword overrides. This is the recommended approach for CI/CD pipelines and production deployments.
Environment Variable |
Required |
Description |
|---|---|---|
|
Yes |
Account email |
|
Yes |
Account password |
|
Yes |
Job name |
|
Yes |
Authentication server URL |
|
Yes |
Orchestrator server URL |
|
No |
CPU cores (default: 2) |
|
No |
Disk space in GB (default: 10) |
|
No |
Delete worker on error (default: false) |
Keyword arguments take precedence over environment variables.
[ ]:
# Create settings from environment variables
# All required env vars must be set: OETC_EMAIL, OETC_PASSWORD,
# OETC_NAME, OETC_AUTH_URL, OETC_ORCHESTRATOR_URL
settings = OetcSettings.from_env()
# Or override specific values via keyword arguments
settings = OetcSettings.from_env(
cpu_cores=8,
disk_space_gb=50,
)
Initialize OETC Handler#
The OetcHandler manages the entire cloud solving process:
[ ]:
# Initialize the OETC handler
# This will authenticate with OETC and fetch cloud provider credentials
oetc_handler = OetcHandler(settings)
print("OETC handler initialized successfully")
print(f"Authentication token expires at: {oetc_handler.jwt.expires_at}")
Solve the Model on OETC#
Now we can solve our model on the OETC cloud platform. The OetcHandler is passed to the model’s solve() method:
[ ]:
# Solve the model on OETC
# This will upload the model, submit a job, wait for completion, and download the solution
import time
print("Starting cloud solving process...")
start_time = time.time()
try:
status, termination_condition = m.solve(remote=oetc_handler, solver_name="highs")
end_time = time.time()
total_time = end_time - start_time
print(f"\nSolving completed in {total_time:.2f} seconds")
print(f"Status: {status}")
print(f"Termination condition: {termination_condition}")
print(f"Objective value: {m.objective.value:.4f}")
except Exception as e:
print(f"Error during solving: {e}")
raise
Examine the Solution#
Let’s examine the solution returned from OETC:
[ ]:
# Display solution summary
print(f"Model status: {m.status}")
print(f"Objective value: {m.objective.value}")
print(f"Number of variables: {m.solution.sizes}")
# Show a subset of the solution
print("\nSample of solution values:")
print("x values (first 5x5):")
print(m.solution["x"].isel(dim_0=slice(0, 5), dim_1=slice(0, 5)).values)
print("\ny values (first 5x5):")
print(m.solution["y"].isel(dim_0=slice(0, 5), dim_1=slice(0, 5)).values)
Advanced OETC Configuration#
Solver Options#
Solver name and options can be configured at two levels:
Settings level — defaults stored in
OetcSettings.solverandOetcSettings.solver_optionsCall level — passed via
m.solve(solver_name=..., **solver_options)
Call-level options override settings-level options. The two dicts are merged (call-time takes precedence), and the original settings are never mutated.
[ ]:
# Settings-level defaults
advanced_settings = OetcSettings(
credentials=credentials,
name="advanced-linopy-job",
authentication_server_url="https://auth.oetcloud.com",
orchestrator_server_url="https://orchestrator.oetcloud.com",
solver="gurobi",
solver_options={
"TimeLimit": 600,
"MIPGap": 0.01,
},
cpu_cores=8,
disk_space_gb=50,
)
advanced_handler = OetcHandler(advanced_settings)
# Call-level overrides: solver_name and solver_options are forwarded
# to OETC and merged with the settings defaults.
# Here MIPGap from settings (0.01) is kept, TimeLimit is overridden to 300.
status, condition = m.solve(
remote=advanced_handler,
solver_name="gurobi",
TimeLimit=300,
Threads=4,
)
Error Handling and Debugging#
When working with cloud solving, it’s important to handle potential errors gracefully:
[ ]:
def solve_with_error_handling(model, oetc_handler, max_retries=3):
"""Solve model with error handling and retries"""
for attempt in range(max_retries):
try:
print(f"Solving attempt {attempt + 1}/{max_retries}...")
status, termination = model.solve(remote=oetc_handler)
if status == "ok":
print("Solving successful!")
return status, termination
else:
print(f"Solving returned status: {status}")
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < max_retries - 1:
print("Retrying in 30 seconds...")
time.sleep(30)
else:
print("All attempts failed")
raise
return None, None
# Example usage (commented out to avoid actual execution)
# status, termination = solve_with_error_handling(m, oetc_handler)
Security Best Practices#
When using OETC in production:
Never hardcode credentials: Use environment variables or secure credential stores
Use token expiration: The OETC handler automatically manages token expiration
Validate inputs: Ensure your model data doesn’t contain sensitive information
Monitor costs: Cloud computing resources have associated costs
Clean up resources: Set
delete_worker_on_error=Truefor automatic cleanup
Comparison with SSH Remote Solving#
Feature |
OETC Cloud |
SSH Remote |
|---|---|---|
Setup |
Account registration |
Server access required |
Scalability |
Auto-scaling |
Fixed server resources |
Maintenance |
Managed service |
Self-managed |
Cost |
Pay-per-use |
Infrastructure costs |
Security |
Enterprise-grade |
Self-managed |
Solver Licenses |
Included |
User-provided |
Choose OETC for: - Large-scale problems requiring significant compute resources - Temporary or intermittent optimization needs - Teams without dedicated infrastructure - Access to premium solvers without license management
Choose SSH remote for: - Existing infrastructure with optimization solvers - Strict data governance requirements - Consistent, long-running optimization workloads - Full control over the solving environment