Using Commercial Solvers (CPLEX / Gurobi)#

By default, EMHASS uses the high-performance open-source solver HiGHS. It is bundled with the default Docker image and requires no configuration.

If you possess a license for a commercial solver like Gurobi or CPLEX, you can enable them by building a custom Docker image.

Note

This functionality is intended for advanced users. This will assume using the Docker Standalone installation method. See Installation methods section for more details.

1. Create a Custom Dockerfile#

Create a file named Dockerfile.custom that extends the official EMHASS image.

For Gurobi:

FROM davidusb-geek/emhass:latest

# Install the Gurobi Python interface
RUN uv pip install gurobipy

# Set the solver environment variable
ENV LP_SOLVER=GUROBI

For CPLEX:

FROM davidusb-geek/emhass:latest

# Install the CPLEX Python interface
RUN uv pip install cplex

# Set the solver environment variable
ENV LP_SOLVER=CPLEX

2. Build the Image#

Build your custom image locally:

docker build -t emhass-custom -f Dockerfile.custom .

3. Run with License Mounting#

When running the container, you must mount your license file to the location expected by the solver.

Example for Gurobi: Assuming your license file is at /home/user/gurobi.lic:

docker run -d \
  --name emhass \
  -p 5000:5000 \
  -v /home/user/config_emhass.json:/app/config_emhass.json \
  -v /home/user/gurobi.lic:/opt/gurobi/gurobi.lic \  <-- Mount License
  -e GRB_LICENSE_FILE=/opt/gurobi/gurobi.lic \       <-- Tell Gurobi where it is
  emhass-custom

Note: You do not need to change any configuration in config.json or the web UI. The LP_SOLVER environment variable handles the switch automatically.

Optimization Performance Tuning#

EMHASS includes several features to improve optimization performance, especially for complex configurations with many deferrable loads and battery systems.

Object Caching & Warm Start#

EMHASS automatically caches the optimization problem structure between runs. When the system configuration hasn’t changed, subsequent optimizations reuse the cached problem structure and warm-start from the previous solution. This can significantly speed up repeated optimizations (e.g., during MPC operation).

The cache is automatically invalidated when:

  • Number of deferrable loads changes

  • Battery configuration changes

  • Thermal load configuration changes

  • Prediction horizon changes

No configuration is required—caching works automatically.

MIP Gap Tolerance#

For Mixed-Integer Programming problems (which occur when using semi-continuous loads, single-constant loads, or startup penalties), the solver can spend significant time proving a solution is exactly optimal. The lp_solver_mip_rel_gap parameter allows the solver to stop earlier when a “good enough” solution is found.

Configuration:

# In config.yaml or config.json
lp_solver_mip_rel_gap: 0.05  # Stop when within 5% of optimal

Valid range: 0 to 1 (representing 0% to 100% gap tolerance). Values outside this range are clamped automatically.

Recommended values:

Value

Description

Use Case

0

Exact optimal (default)

When precision is critical

0.05

Within 5% of optimal

Recommended for most users - ~2x speedup

0.10

Within 10% of optimal

Fast solving, good for testing

0.20

Within 20% of optimal

Very fast, adequate for simple decisions

Benchmarks show:

  • 5% gap: ~1.75x speedup

  • 10% gap: ~1.86x speedup

  • 20% gap: ~2.89x speedup

For home energy optimization, a 5% gap is typically imperceptible in practice—the difference between “optimal” and “within 5% of optimal” is usually smaller than forecast uncertainty.

Note: This parameter only affects problems with binary variables. Pure linear problems (continuous loads only, no battery) are unaffected.

Tuning for Low-Power Hardware (Raspberry Pi, low-end NUC, etc.)#

When EMHASS runs on a Raspberry Pi 4 / 5, an Intel NUC, or similar low-power hardware, several defaults can be tuned to keep the MPC cycle responsive without changing what gets optimised. All settings below are quality-neutral unless explicitly noted otherwise.

The advice below assumes the default HiGHS solver (lp_solver: HiGHS) and a home-sized problem (handful of deferrable loads, 24–48 h horizon). With a different solver backend or a much larger problem, the specific numeric recommendations (e.g. num_threads: 2) may not apply directly — the principle of “leave headroom for HA and the OS” still does.

Quick preset#

In config.yaml (or via the web UI):

# Low-power deployment preset
lp_solver_timeout: 60          # plenty of headroom; never time out on real workloads
num_threads: 2                 # leave one core free for HA / publishing

Then, depending on whether your problem has binary variables (semi-continuous loads, single-constant loads, startup penalties):

  • Pure-LP setups (no binaries) — leave defaults; HiGHS’ simplex is already fast on a Pi.

  • MILP setups — consider lp_solver_mip_rel_gap: 0.05 (see the MIP-gap section above for the quality trade-off).

Why the threads setting matters#

HiGHS’ default behaviour (num_threads: 0) runs the simplex single-threaded and uses HiGHS’ internal parallel scheduler for the MIP branch-and-bound. On a 4-core Pi 4 with no other heavy workloads, num_threads: 2 is typically the sweet spot:

  • More than half the cores helps the B&B but doesn’t help simplex.

  • Fewer than all cores leaves room for Home Assistant, EMHASS’ own publish path, and the OS so optimisation doesn’t preempt other realtime work.

On boxes with 8+ cores, multi-threading can actually hurt small MILPs because of coordination overhead — measure on your specific shape before turning it up.

Other settings worth checking#

  • prediction_horizon: on a Pi, 48 h × 30 min steps (96 steps) is roughly the comfort threshold. 24 h × 5 min (288) is also reasonable. 48 h × 5 min (576) is workable but pay close attention to MPC cycle wall-clock — start with lp_solver_timeout: 120 and decrease once you’ve measured.

  • weather_forecast_method: open-meteo with the built-in 30-min cache is cheap. Solcast adds an HTTP roundtrip per cycle; the cache helps but a slow WAN link from a Pi can still cost a second or two each tick.

  • historic_days_to_retrieve: each day fetched is one HA recorder call per sensor. Default 2 days is fine; bumping to 5+ for a long-window forecast model can add real time on a Pi behind cellular/Tailscale.