Solver selection in FlexTool
FlexTool can dispatch each solve in a scenario tree to a different LP / MIP
solver. The default — and what every existing scenario continues to use
without any extra configuration — is HiGHS, the open-source solver that
ships with FlexTool. Commercial solvers (Gurobi, CPLEX, Xpress, COPT) are
opt-in: install the vendor's Python wrapper, obtain a licence, then set
solver = "<name>" on the affected solve entity in the Spine database.
Solver choice is per-solve, not global, so a scenario can mix (for example) a
large dispatch LP on Gurobi with a small validation solve on HiGHS.
Supported solvers
| Solver | License |
|---|---|
| HiGHS | Free, open source |
| Gurobi | Commercial / academic free / WLS |
| CPLEX | Commercial / academic free / subscription |
| Xpress | Commercial / community (5000 var/row cap) |
| COPT | Commercial / academic free |
Per-solver installation, licensing, and verification:
PLEASE NOTE: The instructions can be out-of-date. Let us know if this is the case by opening an issue.
How to switch solvers
Solver selection lives on the solve entity in the Spine database, in the
solve_advanced parameter group. Each parameter is optional and falls back
to a documented default if omitted, so existing scenarios behave exactly as
they did before this feature was added.
The DB-level solver parameters are, with their per-solver defaults:
| Parameter | Default | One-line description |
|---|---|---|
solver |
"highs" |
Solver name from [highs, gurobi, cplex, xpress, copt] |
solver_arguments |
empty Map | Raw option name → value pairs forwarded verbatim to the solver |
solver_mip_gap |
unset | Relative MIP gap; normalised across solvers |
solver_precommand |
unset | Not used: FlexTool never runs it (kept so older databases still read); a value gives a warning, see Solver precommand |
A handful of options that used to be DB parameters are now controlled by
CLI flags on flextool (they apply to the whole run rather than per
solve):
| CLI flag | Former DB param (removed in v56; a stored value was dropped) | Values / notes |
|---|---|---|
--solver-time-limit |
solver_time_limit |
Seconds per solve, on every solver (HiGHS time_limit, Gurobi TimeLimit, CPLEX timelimit, Xpress MAXTIME, COPT TimeLimit); unset means no limit. See Time and other limits. |
--solver-mip-gap |
Relative MIP gap of every solve, on every solver (HiGHS mip_rel_gap, Gurobi MIPGap, CPLEX mip tolerances mipgap, Xpress MIPRELSTOP, COPT RelGap); wins over the solve's solver_mip_gap |
|
--solver-config-dir |
A folder of the solver option files; repeat for several, highest first (file by file); see Where the option files come from | |
--threads |
Threads of the run (the FlexTool window sends its Threads per run); sets every solver's thread count (HiGHS threads unless --highs-threads is given, Gurobi Threads, CPLEX threads, Xpress THREADS, COPT Threads). See below |
|
--highs-threads |
solver_threads |
HiGHS only: integer thread count (default 1); >1 enables HiGHS parallel mode |
--solver-log-level |
solver_log_level |
silent hides the log of every solver; normal (default) shows it; verbose is HiGHS only |
--presolve, --highs-threads, --user-bound-scale and
--solver-log-level verbose are HiGHS options. When one of them is given and a solve runs on a commercial
solver, the run warns once: set the solver's own parameter in the solve's
solver_arguments or in its option file instead.
--threads (and FLEXTOOL_THREADS; the FlexTool window always sends its
Threads per run, default 1) sets the thread count of every solver: HiGHS
threads, Gurobi Threads, CPLEX threads, Xpress THREADS, COPT
Threads. It replaces the value of the option file and of
solver_arguments, even a value meaning all cores (0; Xpress and COPT
0 or negative). FlexTool splits the computer's cores between the runs it executes in
parallel, so the run's thread budget wins. The run prints one line when it
replaces a different value, for example gurobi uses Threads 1, the run's
thread budget (flag --threads), instead of Threads 0 (all cores) in
<folder>/gurobi.opt. For more solver threads raise --threads (Threads per
run in the FlexTool window), or FLEXTOOL_THREADS when the budget came from
it. A Spine Toolbox run that gives neither takes 1 thread for every
solver (Toolbox executes its runs in parallel), printed as gurobi uses
Threads 1, the run's thread budget (Spine Toolbox run's default), ...;
add --threads N to the FlexTool tool's arguments for more. Without
--threads and FLEXTOOL_THREADS in a terminal run the option file and
solver_arguments keep the thread count. Per-algorithm thread controls
(Xpress BARTHREADS / MIPTHREADS / CONCURRENTTHREADS, COPT BarThreads /
SimplexThreads / CrossoverThreads / MipTasks, Gurobi ConcurrentJobs)
are not changed: a value set there is used as given, even when it is larger
than the run's threads.
The environment variables FLEXTOOL_SOLVER_TIME_LIMIT and
FLEXTOOL_SOLVER_MIP_GAP are the twins of the two flags (the flag wins). The
older names FLEXTOOL_HIGHS_TIME_LIMIT and FLEXTOOL_HIGHS_MIP_GAP still
work, for HiGHS only. See Environment variables.
Example — set Gurobi on a single solve, with a 60-second time limit on each solve of the run:
solve entity: yearly_dispatch
solve_advanced.solver = "gurobi"
flextool <input_db_url> --solver-time-limit 60
Set the DB parameter in Spine Toolbox by selecting the solve entity,
opening its parameter editor, finding the solve_advanced group, and
entering the values above. Unspecified parameters take their defaults.
Solver precommand is not run
FlexTool 3 ran the solve's solver_precommand in front of the CPLEX call,
for example to reserve a floating licence for the solve. FlexTool 4 never
runs it: a command stored in a database would run on the computer of anyone
who opens that database and presses Run, so a database from someone else
could run any program. A database that still has a value loads and solves as
before, with a warning once per solve and run that the command is not run.
To hold a floating licence, use your solver's own licence server setup (for
example a Gurobi token server set in gurobi.lic, or the licence server in
the licence file of CPLEX, Xpress or COPT); the solver then checks a licence
out and back in for the solve by itself. For anything else that ran in front
of the solver, set a solver wrapper on this computer (never in a database):
see Running a solver through a wrapper
and FlexTool 3 precommand users.
Running a solver through a wrapper
A solver wrapper is a program that FlexTool starts in front of Gurobi's,
CPLEX's, Xpress's or COPT's program, for example to hold a floating licence
for the solve, to queue the solve, or to lower the solver's priority
(nice). It is set per solver on this computer only, never in a database, a
project, a workspace or on the command line (a Spine Toolbox project from
someone else carries command lines), in one of two ways (the first wins):
- the environment variable
FLEXTOOL_SOLVER_WRAPPER_GUROBI,FLEXTOOL_SOLVER_WRAPPER_CPLEX,FLEXTOOL_SOLVER_WRAPPER_XPRESSorFLEXTOOL_SOLVER_WRAPPER_COPT: a JSON list of the program's full path and its arguments, or just the program's full path (never split at spaces). Empty or[]means no wrapper, also over the settings file (for CI);
export FLEXTOOL_SOLVER_WRAPPER_GUROBI='["/usr/bin/nice", "-n", "10"]'
wrappersinsolver_settings.jsonin FlexTool's user settings folder (the FlexTool window writes it: Solver options → On this computer):
{"format": 1, "wrappers": {"cplex": ["/usr/local/bin/lic_hold", "--queue", "opt"]}}
Runs from the FlexTool window, the terminal and Spine Toolbox all use the same setting: a Toolbox run reads the environment variable (set it where Toolbox is started) or the settings file of the user running Toolbox.
FlexTool then runs, without a shell, the wrapper's items followed by the solver's own command: the solver program's full path and its arguments.
/usr/local/bin/lic_hold --queue opt /opt/ibm/cplex/bin/x86-64_linux/cplex
The wrapper must start its remaining arguments as a command, pass its
standard input and output through (CPLEX, Xpress and COPT get their commands
on standard input), and return the solver's exit code. A shell script that
ends in exec "$@" does; on Windows, and anywhere, a Python script does:
# lic_hold.py: run with the full path of python as the wrapper program
import subprocess, sys
# ... reserve the licence here ...
code = subprocess.run(sys.argv[1:]).returncode # stdin, stdout and stderr pass through
# ... release it here ...
sys.exit(code)
{"format": 1, "wrappers": {"cplex": ["C:\\Python312\\python.exe", "C:\\tools\\lic_hold.py"]}}
The rules, checked before the first solve of a run and again just before the solver starts:
- the first item is the wrapper program's full path (FlexTool never looks for
it on
PATHor in the current folder), to an existing file, symbolic links followed; executable on Linux and macOS, a.exeon Windows; - a script that Windows runs through a command interpreter (
.bat,.cmd,.ps1,.vbs,.js, ...) and a command interpreter itself (cmd.exe,powershell,pwsh,wscript,cscript) are refused on every system: they would read the solver's path and arguments as a command line. On Windows alsowsl.exeandbash.exe(andsh,zsh,mshta,rundll32,regsvr32,forfiles,conhost,msiexec), which join their arguments into a command line or run what they name. Use a program, orpythonwith a script as above. On Linux and macOS a shell script works, with the script as the program or as the shell's first argument, when it starts the solver withexec "$@"(neversh -cwith the arguments pasted into a command text); - at most 32 items, each at most 4096 characters.
The run's log has the line
Running CPLEX (/opt/.../cplex) through the solver wrapper /usr/local/bin/lic_hold with 2 arguments (from environment variable FLEXTOOL_SOLVER_WRAPPER_CPLEX)
(with --solver-log-level silent it goes to the log only): the program and
the number of its arguments, never the arguments, because the AI assistant
can send the run log to its provider. The run record (_run_status.json,
on this computer) has solver_wrappers with the items in full. A failure
message says the solve was started through the wrapper, since the wrapper
may be the cause. Keep passwords and tokens out of the wrapper's items
anyway: the run record sits in the project's result folder, which may be
synced or shared; let the wrapper read secrets itself. A project package leaves the run log out and
keeps only the wrapper's program in the run record, with the number of its
arguments. The solver program is found in the absolute folders of PATH
(and, on Linux and macOS, the usual install folders), never in the current
folder. HiGHS runs inside FlexTool and has no wrapper; to lower its priority,
start FlexTool itself under nice. Watchdog, Ctrl-C and the stop of the
solver's processes work through the wrapper: FlexTool stops the wrapper and
everything it started.
FlexTool 3 precommand users
FlexTool 3 ran a solve's solver_precommand in front of CPLEX. FlexTool 4
does not run it (see Solver precommand is not run);
set the same program once per computer as the CPLEX wrapper instead:
- Find the program's full path (FlexTool 3 found it on
PATH):which lic_holdon Linux and macOS,where lic_holdon Windows. - Set it: in the FlexTool window, Solver options → On this computer, CPLEX
program; or
FLEXTOOL_SOLVER_WRAPPER_CPLEX=/full/path/lic_hold. If the precommand had arguments, give them one per line in the window, or as a JSON list in the variable:["/full/path/lic_hold", "--queue", "opt"]. - Check what the program does with its arguments. FlexTool 3 gave it
cplex -c "read ..." ...; FlexTool 4 gives it the full path of the CPLEX program alone and sends CPLEX's commands on standard input. A program that only runs"$@"(orsubprocess.run(sys.argv[1:])) works unchanged; one that read the CPLEX arguments must be changed. A.bator.cmdmust become a program, for example the Python script above. - Run once and look for the
Running CPLEX (...) through the solver wrapperline. The database'ssolver_precommandcan stay (it is never run, and gives a warning per solve); clear it to stop the warning. The same works for Gurobi, Xpress and COPT with their own variables.
The parameters in detail
| Parameter | Type | Default | Description |
|---|---|---|---|
solver |
str | "highs" |
Solver name. Must be one of highs, gurobi, cplex, xpress, copt. |
solver_arguments |
map | empty | Map of option name → value passed verbatim to the underlying solver. Use this for any solver-native option that is not one of the convenience knobs below. Raw options always override the convenience knobs on key collision. |
solver_mip_gap |
float | unset | Relative MIP optimality gap (e.g. 0.01 for 1 %). Translated to each solver's native parameter (MIPGap / mip tolerances mipgap / MIPRELSTOP / RelGap / mip_rel_gap). --solver-mip-gap wins over it. |
solver_precommand |
str | unset | Not used: FlexTool never runs this command (FlexTool 3 ran it in front of CPLEX). The parameter is kept only so that older databases still read; a value gives a warning once per solve. See Solver precommand. |
The corresponding CLI flags (--solver-time-limit, --solver-mip-gap,
--highs-threads, --solver-log-level) are documented in the table above.
Authoring solver_arguments in Spine
solver_arguments is a Spine Map of string keys to string-or-float values.
Keys are the solver's own parameter names (e.g. Presolve for Gurobi or
presolve for HiGHS), values are whatever that solver expects.
Example (Gurobi, in Spine Toolbox's map editor):
solver_arguments:
Presolve = 2
Method = 2
Heuristics = 0.1
These are passed through untouched, within the limits of
What the database may set. Parameter names match
the way the solver reads them: without regard to case (mipgap and MIPGap
are one parameter); Gurobi also ignores _ (MIP_Gap is MIPGap) and Xpress
an XPRS_ prefix; for CPLEX a dotted name such as mip.tolerances.mipgap is
the same as mip tolerances mipgap. Write CPLEX names in full: from the
database an abbreviation is refused (the message gives the full name), and in
an option file it is not matched to its full name when the layers are merged.
A name that is not one of the solver's parameters is refused from the database
(see below); in your own option files it gives a warning and is passed on, and
what the solver does with it is the solver's own: Gurobi ignores it with a
message, which FlexTool repeats as a warning naming where the name was set.
FlexTool's results are not affected by a solver's file parameters (they are in
output_parquet/<scenario>/ and the outputs you chose). Gurobi's ResultFile
is FlexTool's own: FlexTool names the result file and reads the solution from
it, so a ResultFile set anywhere is ignored with a warning. A commercial
solver's log is saved as <work folder>/solve_data/subprocess/<solve>.log
(see below). HiGHS' log is part of the run's console output, which
flextool-run and the FlexTool window save in the run's log,
output_parquet/<scenario>/_run.log.
What the database may set
An input database can come from someone else, so FlexTool trusts
solver_arguments and solver_mip_gap less than the files and the command
line of your own computer. The solver option files (solver_config/<solver>.opt),
the flags and the environment variables keep full paths and server
parameters; from the database:
- Only the solver's own parameter names are taken. FlexTool has a list of every parameter of each solver, made from the solver's own package:
| Solver | List made from |
|---|---|
| HiGHS | HiGHS 1.15.1 (highspy) |
| Gurobi | Gurobi 13.0.2 (gurobipy), with gurobi_cl's own parameters |
| CPLEX | CPLEX 22.1.2 (the cplex package), with the Interactive Optimizer's logfile, defaults and distmip config from IBM's documentation |
| Xpress | Xpress 46.01.02 (xpress 9.8.1) |
| COPT | COPT 8.0.4 (coptpy), with the console's LogFile |
A name that is not in the list is refused, and the message suggests the
closest listed name: a misspelling (MIPGapp), a name in the wrong case
for HiGHS (Presolve; HiGHS takes presolve only), a CPLEX abbreviation
(mip tol mipgap: the message says to write mip tolerances mipgap), or a
parameter of a newer solver version than the list. For HiGHS, a name that
the installed HiGHS knows as a number or a switch is taken as well, so
updating HiGHS does not stop your runs; a text option of a newer HiGHS is
not. Rare and advanced parameters of the listed versions are in the list.
Names are matched as the solver reads them (above).
- Ordinary parameters (gaps, tolerances, methods, thread counts, switches)
are taken as they are. A text parameter that names no file (CPLEX
read fileencoding and cpumask, Xpress MPSRHSNAME and the like) takes
no /, \ or : from the database; none of their values needs them.
- A parameter that names a file or folder takes only a plain file name:
ASCII letters, digits and _ . + -, starting with a letter, digit or _,
with no /, \, : or ... The file is written in the solve's work
folder, <work folder>/solve_data/subprocess/. A full path is refused.
These are the parameters whose value is a file or folder name:
| Solver | File and folder parameters |
|---|---|
| HiGHS | log_file, solution_file, read_solution_file, read_basis_file, write_basis_file, write_model_file, write_presolved_model_file, write_iis_model_file, mip_improving_solution_file |
| Gurobi | ResultFile, LogFile, SolFiles, NodefileDir, InputFile, ConcurrentSettings, MultiObjSettings, TuneBaseSettings, TuneIgnoreSettings, TuneParams |
| CPLEX | logfile, workdir, output intsolfileprefix |
| Xpress | TUNERMETHODFILE, TUNEROUTPUTPATH, TUNERSESSIONNAME |
| COPT | LogFile |
A number or a switch in a parameter whose name has file, dir or path
in it (Gurobi NodefileStart 0.5), or that writes files of its own (Gurobi
and CPLEX Record), passes. Every solver runs in the same work folder, so
the file name may not be one that a solver reads by itself from there:
gurobi.env, a name ending in .lic, license.dat, license.key,
client.ini, xpauth.xpr or nethasp.ini (in any case).
- Server, cloud and licence parameters are refused, whatever their type
(an integer can switch remote solving on). Gurobi: ComputeServer,
ServerPassword, ServerTimeout, the CS... Cluster Manager parameters
(CSManager, CSAPIAccessID, CSAPISecret, CSGroup, CSPriority,
CSRouter, ...), the Cloud... Instant Cloud parameters, the WLS... Web
License Service parameters, TokenServer, TSPort, LicenseID,
Username, WorkerPool and WorkerPassword. Xpress: COMPUTE, which
sends the solve to a remote service, and the other COMPUTE... controls
(COMPUTEEXECSERVICE, COMPUTEJOBPRIORITY, COMPUTELOG). CPLEX:
distmip config (the machines of a distributed MIP solve).
- A few others are refused as well: Gurobi's JobID and Dummy (text
parameters with no use from a database) and CPLEX's defaults (a command
that resets every parameter, not a parameter).
- FlexTool 3's HiGHS settings in a solve that uses Gurobi, CPLEX, Xpress
or COPT are left out with one warning per run: the database migration moved
FlexTool 3's highs_method, highs_parallel and highs_presolve into
solver_arguments as solver, parallel and presolve for every solve.
A number for the solver's own parameter of that name (Gurobi presolve 2)
is still passed.
The error names the parameter, its value and the solve. FlexTool checks the
solver_arguments of every solve the model runs (model.solves and the
solves they contain) before the first solve starts, so the run stops in
seconds, not after hours of earlier solves. A solve that the model does not
run is not checked: its options never reach a solver. Put a
file path, a server parameter or a parameter that is not in the list in the
solver's option file, solver_config/<solver>.opt in the project's or the
workspace's solver_config (or a folder given with --solver-config-dir),
which keeps them when the folder is trusted (see
Which option folders are trusted). An
option file that came in a project package from someone else gets these
rules in the project's runs until you trust the project's solver option
files, and is checked by them when it is imported: a file with a line they
refuse is kept as <solver>.opt.review, which no run reads. To use it as it
is, read it, rename it to <solver>.opt and tick "Trust this project's solver
option files" (Solver options → On this computer, with the project open), or
run flextool-trust-workspace <project folder> in a terminal.
Exporting a project applies the same rules to the option files it would
ship: a file with such a parameter is left out of the package, and the export
names its lines (a server password or a folder of your computer should not be
sent unseen). A CPLEX parameter name in cplex.opt is written without the
leading set (FlexTool adds it), and a name that starts with set is refused
from every source. A UTF-8 byte order mark at the start of an option file is
ignored.
The checks against extra commands (no line breaks, spaces, quotes, ;, #,
= or a backslash at the end) apply to every source. So does the rule that
every parameter has a value: an empty value is refused for every solver
(HiGHS included), and for Gurobi, CPLEX, Xpress and COPT also an option-file
line with a name and no value (for CPLEX also a last word that is not a
number where the parameter takes one, such as mip tolerances mipgap
without its value, and a group of parameters given a value, such as
mip tolerances 0.01: CPLEX would ask which parameter and read the
script's next line as the answer). The solution-file lines of old highs.opt templates are
dropped, as before.
Allowing a parameter that is not in the list
A newer solver version can have parameters that FlexTool's list does not
have yet. Set such a parameter in solver_config/<solver>.opt, which takes
any name (with a warning). Or allow names that are not in the list from input
databases on your computer, in one of two ways (the first wins):
- the environment variable
FLEXTOOL_ALLOW_UNLISTED_SOLVER_PARAMETERS=1(0refuses them again); or - the file
solver_settings.jsonin FlexTool's folder of your user settings (~/.config/flextool/on Linux,%LOCALAPPDATA%\flextool\on Windows,~/Library/Application Support/flextool/on macOS):
{"format": 1, "allow_unlisted_parameters": true}
The setting is on your computer only: no database, project, workspace or command-line flag can switch it on, since those can come from someone else. With it on, an unlisted name is passed with one warning per run that says where the setting is. The rules for paths, servers and the files a solver reads by itself still apply:
- an unlisted name takes only a number, a switch or a plain file name (as a
file parameter above), never a path or a name such as
gurobi.env; - a name that looks like a server parameter (Gurobi
CS...,Cloud...,WLS...,Server...,Token...,Worker...; XpressCOMPUTE...; CPLEXdistmip ...) is still refused, as are the listed ones above; - a CPLEX abbreviation of a listed name (
logforlogfile,d cfordistmip config,deffordefaults) is still refused: the switch is for a newer solver's parameters, not for abbreviations; - a HiGHS text option that FlexTool's list does not have is still refused. The
run's record (
output_parquet/<scenario>/_run_status.json,solver_config) says whether the setting was on and where it came from.
Per-solver baseline .opt files
Every solver gets a baseline parameter file in a solver_config/ folder
(which folder: see Where the option files come from).
These are user-local files, not committed to the repo: each ships in the
wheel as a solver_config/<solver>.opt.template, and flextool-update and
the GUI seed the workspace's solver_config/<solver>.opt from its template
(and re-seed any that are missing). The runtime files are gitignored, so you can edit them —
or delete one to reset it to the bundled default — with zero git-committed
change. The FlexTool window (at start) and flextool-update replace a
workspace solver_config/<solver>.opt that is an unedited copy of an earlier
FlexTool default with the current one; an edited file is never touched. The
option values are the same, so no solve changes.
| Solver | Baseline file | Native format |
|---|---|---|
| HiGHS | solver_config/highs.opt |
key=value per line, # comments |
| Gurobi | solver_config/gurobi.opt |
ParamName value per line, # comments |
| CPLEX | solver_config/cplex.opt |
name value per line; name uses interactive set syntax (e.g. mip tolerances mipgap) |
| Xpress | solver_config/xpress.opt |
CONTROL value per line |
| COPT | solver_config/copt.opt |
ParamName value per line |
FlexTool reads results from the solver directly and needs no HiGHS solution
file, so write_solution_to_file, solution_file and write_solution_style
are ignored in highs.opt (older templates set them; projects created then
still have the lines). To have HiGHS write a solution file anyway, set these
options in the solve's solver_arguments (solution_file takes a plain file
name there; the file is written in <work folder>/solve_data/subprocess/).
FlexTool reads each solver's baseline once at the start of the run, overlays
the solve's solver_mip_gap, its solver_arguments Map and the run's flags
parameter by parameter (one value per parameter, the highest layer wins),
writes the merged result next to the MPS file as <solve>.opt (the record of
what the solver received; nothing reads it back) and hands each parameter to
the solver via the per-solver mechanism:
| Solver | How FlexTool hands the merged parameters to the CLI |
|---|---|
| HiGHS | --options-json=<solve>.options.json on the flextool.cli.cmd_solve_mps subprocess (--save-memory), each option set in order; --options=<solve>.opt reads the record for a replay by hand |
| Gurobi | gurobi_cl <Name>=<value> ... ResultFile=<sol> <mps>: one Name=value argument per parameter |
| CPLEX | Each parameter emitted as set <name> <value> on the interactive optimizer's stdin before read |
| Xpress | Each parameter emitted as setControl <NAME> <value> on the optimizer console's stdin before readprob |
| COPT | Each parameter emitted as set <ParamName> <value> on copt_cmd's stdin before read |
Override precedence (highest wins), the same for every solver:
- The run's flags:
--solver-time-limitand--solver-mip-gap(orFLEXTOOL_SOLVER_TIME_LIMIT/FLEXTOOL_SOLVER_MIP_GAP; the GUI's time limit is sent as the flag), and the run's--threads(orFLEXTOOL_THREADS; the GUI's Threads per run is sent as the flag) as the solver's thread count. HiGHS also takes--highs-threads,--presolveand--solver-log-levelhere (a commercial solver's log is hidden by--solver-log-level silenttoo, but that is not one of its parameters). - Raw
solver_argumentsentries on the solve. - Translated convenience knobs (
solver_mip_gap). - The baseline file
<solver>.opt.
For HiGHS every solve receives this one layered set: warm and cold solves,
--save-memory and the subprocess solve of warm=False alike. FlexTool's own
determinism defaults sit below the baseline file.
Before each solve the run prints what the solver receives: Solver args: for
HiGHS (the options set above FlexTool's defaults), and for a commercial solver
Solver args (<solver>): with every parameter under its native name, for
example Solver args (gurobi): Threads=1, MIPGap=0.001, TimeLimit=1800.
A commercial solver's program runs in <work folder>/solve_data/subprocess/.
FlexTool writes there the model (<solve>.mps), the parameters passed
(<solve>.opt) and, for CPLEX, Xpress and COPT, the command script it pipes
to the program (<solve>.script). To replay a solve by hand, run the program
in that folder with the script as its input, for example
cd solve_data/subprocess && cplex < y2020_dispatch.script. The solver's
result file and its own log file (gurobi.log, cplex.log) are written there
too.
The solver's log is shown as the solver writes it, in the terminal, the
FlexTool window (greyed, like HiGHS' log) and the run log, after a line
Running <solver> (<program>), and it is saved as <solve>.log in the same
folder. Like the other files there, <solve>.log holds the last roll of a
rolling solve. A failed solve's message points to the log instead of repeating
it. With --solver-log-level silent the log is not shown (nor the Running
line), but FlexTool still reads it to know how the solve ended, saves it, and a
failure message then carries its last 20 lines. If FlexTool stops the solver
(the time-limit watchdog, Ctrl-C, a failure), it also stops the processes the
solver started.
Where the option files come from
A run reads each solver's file (highs.opt, gurobi.opt, cplex.opt,
xpress.opt, copt.opt) file by file from an ordered list of
solver_config folders, the ladder: each solver's file comes from the first
folder of the ladder that has it, and a solver that no folder has uses
FlexTool's default (the shipped template). The ladder is the first of:
- the folders you give or the FlexTool window gives, and only these:
--solver-config-dir PATH(repeat the flag for several folders, highest first) or theFLEXTOOL_SOLVER_CONFIG_DIRenvironment variable (several folders separated by:, on Windows;, as inPATH), and the folders a launcher found,--workspace-solver-config-dir PATH(repeatable, above the others). Nothing from the database's folders or the current folder is added. The FlexTool window (and the calibration it starts) always sends the project'ssolver_config/(when the project has that folder), then the workspace'ssolver_config/, as--workspace-solver-config-dir; whenFLEXTOOL_SOLVER_CONFIG_DIRis set for the window, its folders replace the workspace's folder (passed on as--solver-config-dir) and the project's stays on top; - the input database's folders:
solver_config/next to the input database, alone;- for a database in a GUI project
(
<workspace>/projects/<project>/input_sources/), the project'ssolver_config/, then the workspace's: the same files as a run of the project from the FlexTool window.
The search goes no further up, so a solver_config folder somewhere above
an unrelated database is never picked up;
3. for a Spine Toolbox run with --project-folder-file, the solver_config/
of that Toolbox project;
4. solver_config/ in the current folder;
5. none found: FlexTool's defaults (the shipped templates), with a warning
that lists the folders tried.
So a project's runs take each solver's options from the project's
solver_config/, else the workspace's, else FlexTool's default. To give a
project its own Gurobi options, for example, put just a gurobi.opt in
<workspace>/projects/<project>/solver_config/: its runs still use the
workspace's highs.opt. Delete the project's file to use the workspace's
again.
A project's solver_config/ never gives an unedited FlexTool template (of
this or an earlier version): that file holds the options FlexTool shipped then
rather than a choice made for the project, and
the workspace's file of that solver is used instead (the header names the
skipped file). Tiers 2 to 4 count only when one of their folders holds a
<solver>.opt. They skip solver_config/ in the current folder when every
.opt in it is an unedited FlexTool template and the current folder is not a
FlexTool folder: FlexTool 4.3 and older left such a copy in the folder a run
was started from. The run then takes the next tier and warns, naming the
skipped folder; to use your own option files, pass
--solver-config-dir <the folder with your files>, and delete the skipped
folder to stop the warning. The FlexTool folder itself (the workspace, which
holds templates/project_folder.txt) is never skipped: its solver_config/
is the folder the FlexTool window uses, and a run from there uses it without
a warning, edited or not.
A Spine Toolbox run (whose Data Store is the workspace's input_data.sqlite)
therefore uses the workspace's solver_config, even though Toolbox runs the
tool in a temporary folder.
The run header and the log say which folder was used and why, for example:
Solver config: /home/me/flextool/solver_config (next to the input database)
With several folders they name the folder of each file, then the solvers that use FlexTool's defaults:
Solver config: /home/me/flextool/projects/P/solver_config (project of the FlexTool window): gurobi.opt; /home/me/flextool/solver_config (workspace of the FlexTool window): highs.opt; FlexTool defaults: cplex.opt, xpress.opt, copt.opt
A folder of the ladder that does not exist gives a warning; its solvers use the next folder or FlexTool's default.
Which option folders are trusted
The files of a folder you name (--solver-config-dir,
FLEXTOOL_SOLVER_CONFIG_DIR) and FlexTool's shipped defaults are trusted:
they may use full file paths, server and licence settings, and parameter
names that are not in FlexTool's list (with a warning). A folder that a run
finds by itself (the FlexTool window's --workspace-solver-config-dir
folders, and tiers 2 to 4) may have come from someone else, inside a
workspace, a project, a model folder or a Spine Toolbox project that was
shared or unzipped. Its files are trusted only when the folder it is in (the
workspace; for projects/<project>/solver_config, the project) is one this
computer set up; otherwise every line gets the input database's rules (see
"What the database may set"): a file option takes only a plain file name,
placed in the solve's work folder; no server or licence settings; only
parameter names in FlexTool's list. Trust is judged per folder, so file by
file: when a project's untrusted gurobi.opt is used over a trusted
workspace's, the project's file gets the database's rules, and the
workspace's highs.opt keeps its trust. A file that is byte for byte one of
FlexTool's shipped templates is trusted wherever it lies. A refused line stops
the run with a message that names the file, the line, and how to trust the
folder. The Solver config: line says not a workspace this computer set up
for such a folder, and the run record's solver_config.layers[].trusted and
solver_config.files[].trusted are false.
| Where the folder comes from | Trusted |
|---|---|
--solver-config-dir, FLEXTOOL_SOLVER_CONFIG_DIR |
always |
--workspace-solver-config-dir (the FlexTool window), next to the database, the database's project or workspace, the Toolbox project, the current folder |
when its workspace (or project) is on this computer's list |
| FlexTool's shipped defaults | always |
This computer's list is trusted_workspaces in solver_settings.json in
FlexTool's user settings folder (on Linux ~/.config/flextool/, on Windows
%LOCALAPPDATA%\flextool\, on macOS ~/Library/Application Support/flextool/),
outside every workspace, so nothing that comes with a workspace can add to it.
A workspace gets on the list:
- when
flextool-update, or the FlexTool window at its start, creates itssolver_config/, or finds nothing in it but FlexTool's own default files (unedited.optfiles of this or an earlier version; a symbolic link does not count). A workspace received with edited option files is therefore never listed by being opened; - when you tick "Trust this workspace's solver option files" in the FlexTool window (Solver options → On this computer), after reading the files.
You can also trust a folder from a terminal: flextool-trust-workspace
<folder> (the workspace, or a GUI project's folder) shows the option files of
its solver_config/ that differ from FlexTool's defaults, line by line, and
asks before it lists the folder; --remove takes it off the list, --list
shows the list (and also takes off a folder that no longer exists). It is a
command you type and answer in a terminal, not a flag of a run: a Spine Toolbox
project carries command lines for its tools, so a run flag could come with a
project from someone else, while anything that could start this command unasked
can already run any program as you. It therefore refuses piped input (echo y
| ...) and has no --yes. Control and bidirectional characters in the files
(an escape sequence that would erase a line from the screen) are shown as
escapes such as \x1b, and the line is marked. What you answer for is what
you saw: if a file changes between showing and your answer, nothing is
trusted and the command says which file changed. The window's box does the
same with the files as the dialog found them when it opened.
The trust is for the folder as it was when it was listed, not for its path:
- a different folder put at the same path later (the trusted one deleted or moved away, and a copy from someone else unpacked or copied there) is not trusted; the message says the folder was replaced. FlexTool tells the folders apart by their identity: on Linux and macOS the device, the inode number and the time the folder was made (where the file system records it: ext4, btrfs, XFS, APFS; not NFS or SMB shares); on Windows the volume serial number, the file ID and the creation time. Moving, restoring or copying the folder to another path or computer therefore asks for the trust again, and so can a network drive or a macOS external volume mounted again (its device number can change);
- an option file that changed since, or appeared since, is trusted when your
own account wrote it and no other account can change it: on Linux and macOS
you own it, no other account can write it, and its group can write it only
when the group is your own private group (the one named after you, with no
other members). Otherwise (another owner, writable by others, a link) it gets
the database's rules, with a message naming it, until you trust the folder
again (the box, or
flextool-trust-workspace). On Windows FlexTool does not read files' owners and access lists: a file in a folder that is still the one you trusted counts as your own edit. Inside your user profile folders (C:\Users\<you>) Windows lets no other ordinary account write, but a folder made elsewhere, such asC:\flextool, lets every account that signs in to the computer change its files: on a shared computer, keep trusted workspaces in your profile. A drive that gives files no ID (some network drives) gets no such pass: there any change asks to be trusted again; - on a network drive every change counts as yours: a share mounted for your account shows every file as your own, also the ones a colleague changed;
- so editing your own files never asks again, but files you unpack or copy
into a trusted folder yourself are your edits too, and so are changes that a
sync tool (OneDrive, Dropbox, a network share) or
git pullwrites with your account. Do not trust a folder that other people can change through such a tool; name it with--solver-config-dirfor the runs that should use it.
Entries in trusted_workspaces must be full folder paths: an entry such as .
or ~/flextool makes the list unreadable (nothing is trusted, and the message
says why). Entries written by FlexTool 4.4 development versions (plain folder
names) are bound to the folder at that path, with its files as they are, the
first time FlexTool reads them. FLEXTOOL_USER_HOME or XDG_CONFIG_HOME
naming a relative folder is ignored with a warning (the platform's folder is
used); a full path is used wherever FlexTool starts. A settings file that lies
inside a folder judged for trust makes that folder's trust unreadable, with a
message: a workspace could otherwise carry its own list. The settings file
must be UTF-8 text, and on Linux and macOS owned by you and writable by you
only (FlexTool writes it so): a solver wrapper named in it runs as you, so a
file that another account owns or can write is not read, and the message says
how to fix it.
Spine Toolbox projects are code. A Toolbox project runs the tool scripts and command lines it carries, so it can do anything you can, solver options included: open a Toolbox project, or a launcher script that comes with a workspace, only from people you trust, as you would a program.
A GUI project (for its own projects/<project>/solver_config/) gets on the
list when the FlexTool window creates it, when the window opens it while its
solver_config/ is missing or holds nothing but FlexTool's default files and
no received file waits for review (no <solver>.opt.review, no
imported_solver_config/), and when you tick "Trust this project's solver
option files" (Solver options → On this computer, with the project open). A
project imported from a package is never listed by being imported or opened
with its shipped files in place, and importing at the path of a project
deleted earlier first takes that path off the list; renaming a project in the
window keeps its trust, deleting it removes it. Copying a received file into
a trusted folder (the workspace's solver_config/, a listed project's, or a
folder you name with --solver-config-dir) makes it trusted there: read it
first.
For a workspace whose option files you edited before this check existed, tick
that box once (or pass the folder with --solver-config-dir). On CI or a
server, pass the folder with --solver-config-dir or set
FLEXTOOL_SOLVER_CONFIG_DIR in the job's environment: a folder named that way
is trusted on every run, and nothing needs to be listed. A fresh checkout
where flextool-update creates solver_config/ is listed too (in the user
settings folder of the user who ran it).
The run reads the files once at its start: every roll and solve of the run
uses them as they were then, and an edit during the run reaches only the next
run. The files the run used are copied to
<work folder>/solve_data/solver_config/ as its record; the copy is written
afresh every run and never read back. Up to FlexTool 4.3 runs read a copy in
<work folder>/solver_config/; such a folder is no longer read as the work
folder's copy (the run warns and names it when it differs from the files the
run uses). When the work folder is also the current folder, the folder is
tier 4: it is used when you have edited a file in it (or it is the FlexTool
folder), and skipped, with a warning, while it holds only the templates 4.3
copied there. To run with the files of a particular folder, pass
--solver-config-dir <folder>.
Time and other limits
--solver-time-limit (and the GUI's time limit) applies to each solve
separately: each roll of a rolling solve, and each master and region solve of
a Benders decomposition, gets the full limit. The solver enforces it. The same
rule holds for the solver's other search limits, set in its option file or the
solve's solver_arguments: a node, leaf, solution-count, work or iteration
limit (HiGHS mip_max_nodes, mip_max_leaves, mip_max_stall_nodes,
mip_max_improving_sols, simplex_iteration_limit, ...; Gurobi NodeLimit,
SolutionLimit, WorkLimit, IterationLimit; CPLEX mip limits nodes,
mip limits solutions; Xpress MAXNODE, MAXMIPSOL; COPT NodeLimit), and for an objective target you
set (HiGHS objective_target, Gurobi BestObjStop and BestBdStop; HiGHS
objective_bound works differently, see below). When a solve stops at one
of them:
- a MIP with a solution found keeps that solution, with a warning naming the solve, the limit, its option and, when the solver reports it, the gap reached; the run goes on;
- an LP, or a MIP without a solution, stops the run with an error naming the limit and its option (an LP stopped at a limit has no usable solution);
- in a Benders decomposition, a master or region solve stopped by any limit stops the run with an error, MIP or not: the decomposition needs each of its solves to be optimal.
HiGHS has one status for its node, leaf, stall-node and improving-solution limits, so its warning names all four options.
A memory limit or an interrupt (an aborted solve) stops the run, naming the solver's status, even with a solution. So does a solution the solver does not certify as optimal, such as CPLEX's "optimal with unscaled infeasibilities" or "optimal face unbounded", or Gurobi's "suboptimal".
A HiGHS MIP solved with objective_bound set (in highs.opt or the solve's
solver_arguments) gets a warning, once per solve, even when HiGHS reports it
optimal: HiGHS treats the bound as a cutoff and reports the best solution
within it as optimal, so it may not be the optimum. Remove objective_bound
for a proven one.
Objective values are in unscaled currency. An option that is an objective
value — a target, bound or cutoff, or an absolute gap — is written in the
currency of the model's cost data (not the million currency of the cost
outputs). The solver works on a
scaled objective: the cost times the solve's scale_the_objective (default
1e-6) and, for HiGHS, times 2^user_objective_scale (set by the autoscaler, or
by you). FlexTool scales these options by the same factors before the solver
gets them, in every solve path: in-process HiGHS, --save-memory and the
commercial solvers' option files. The Solver args line shows the value the
solver gets and, in parentheses, the value you wrote, for example
mip_abs_gap=0.5 (500000 in unscaled currency). The options:
| Solver | Objective level (target, bound, cutoff) | Objective difference (absolute gap) |
|---|---|---|
| HiGHS | objective_target, objective_bound |
mip_abs_gap |
| Gurobi | BestObjStop, BestBdStop, Cutoff |
MIPGapAbs, PoolGapAbs |
| CPLEX | mip tolerances lowercutoff / uppercutoff, mip limits lowerobjstop / upperobjstop, simplex limits lowerobj / upperobj |
mip tolerances absmipgap, mip tolerances objdifference, mip pool absgap, mip polishafter absmipgap, barrier limits objrange |
| Xpress | MIPABSCUTOFF |
MIPADDCUTOFF, MIPABSSTOP |
| COPT | (none) | AbsGap |
Relative gaps and tolerances (mip_rel_gap, MIPGap, --solver-mip-gap,
...) have no unit and are passed as written. A value of 1e20 or more, or
inf, means "off" and is passed as written. In a rolling solve each roll
compares the value with its own objective (each roll's cost, not the whole
horizon's); in a stochastic solve, with the expected cost over the branches.
A Benders solve refuses an objective target or bound, naming the option and
where it was set: a whole-model level has no meaning for the master or a
region. Its absolute gaps are passed, scaled for each part.
FlexTool reads how a solve ended from the solver's result file (HiGHS, Gurobi's
JSON result, CPLEX) or from its console log (COPT, Xpress; Gurobi when it
writes a text result file). With COPT's log turned off (Logging 0 or
LogToConsole 0), or a text Gurobi result with LogToConsole 0, a finished
solve cannot be told from one stopped by a limit, so the run stops and says
so: keep the log on (the shipped option files do). Xpress: FlexTool knows
only its time limit and an unfinished solve, from the console and the print
solution; another Xpress limit with a solution is not told apart (FlexTool's
Xpress driver solves LPs only).
FlexTool also stops a solver that has not finished well after its limit (after twice the limit plus 10 minutes), which only guards against a solver that hangs: reading the model and writing the result are not counted in the solver's own limit.
Warm-start caveat
The cross-solver dispatch always builds a fresh solver instance per call.
HiGHS retains its existing warm-start fast path because FlexTool uses HiGHS
directly (not via the cross-solver solve() entry point) when no solver
parameter is set. Picking any non-HiGHS solver disables the in-process
warm path for that solve: each sub-solve is built and solved through the
cross-solver dispatch. Rolls of a rolling-horizon scenario are built anew on
every solver (HiGHS reuses a built model only for an identical sub-solve), so
the solver choice itself does not change how often the LP is built.
Benders-decomposition caveat
decomposition_method = benders_regional is currently HiGHS-only.
Setting a non-HiGHS solver on a Benders-decomposed scenario raises a
FlexToolUserError at startup. The error names both the offending solve and
the two remedies (either set solver = highs for that solve, or remove the
Benders decomposition from the group).
This is an upstream constraint in polar-high: the Benders master is a
persistent WarmProblem, and warm-LP machinery is HiGHS-only by design.
Lifting the restriction is tracked separately; in the meantime FlexTool
fails loudly rather than silently running on HiGHS.
The layered options above also reach the Benders master and every region
subproblem. Benders' own settings stay on top of them: the region
subproblems always run crossover (or the interior-point settings of the
experimental interior-dual mode), and the per-solve HiGHS log is silenced.
While the regions are solved in parallel (more than one worker), threads
and parallel (--highs-threads) apply to the master only; the regions stay
single-threaded so that the result does not depend on the worker count. HiGHS
has one thread scheduler per process, so the master re-initialises it before
each of its solves (polar-high 3.10.0 or later; with an older polar-high the
thread count is not used while the regions run in parallel). A
time limit (--solver-time-limit, or time_limit in solver_arguments)
applies to each master and region solve separately, not to the whole
decomposition. A master or region solve that stops at any limit stops the run
(see "Time and other limits" above).
Verifying which solvers are installed
To see which solver Python wrappers polar-high has detected on this system, run:
python -c "from polar_high.solvers import available_solvers; print(available_solvers)"
Example output on a developer machine with all five wrappers installed:
['gurobi', 'cplex', 'xpress', 'copt', 'highs']
A solver appears in available_solvers if its Python wrapper is
importable. That is not the same as "the solver is usable" — license checks
happen later, when FlexTool actually dispatches a solve. The most common
failure mode is available_solvers lists (say) gurobi but the first solve
raises a FlexToolUserError about a missing licence file; see the relevant
per-solver page for the licence setup.
Common pitfalls
- Setting
solverwithout installing the wrapper. Run the verification command above before authoringsolver = "gurobi"(or similar) on a solve. If the solver name is missing fromavailable_solvers, the per-solver page has thepip installline you need. - Setting
solverwithout a valid licence. The wrapper installs without one; the licence check only fires at solve time. FlexTool's error will point you at the licensing section of the solver page. - Mixing solvers without checking. A scenario tree can route each solve
to a different solver, but every solve needs its own
solverparameter if you want anything other than HiGHS. Unset solves stay on HiGHS. - Commercial solvers on very large models. A non-HiGHS solver is always
given the model as an MPS file and run in its own process. The trade-off is bounded peak RSS (the
Problem.write_mpswriter caps ~2-3 GB on the largest LPs) at the cost of file I/O plus a fresh solver process per sub-solve. - Setting the same parameter in several places. One value reaches the
solver, from the highest layer of the precedence list above. If you pass
--solver-time-limit 60and also setsolver_arguments = {TimeLimit: 30}on a Gurobi solve, Gurobi seesTimeLimit = 60; without the flag it sees30. Asolver_argumentsentry wins oversolver_mip_gapand over the option file. - Xpress and integer variables. FlexTool runs
mipoptimizefor a solve with integer variables (integer investments, unit online variables) andlpoptimizefor an LP. A MIP's status comes from the Xpress log; a MIP that ends without an integer solution stops the run (it never uses the LP relaxation). See Xpress.