Documentation
Get started
Install SHOT, solve a first problem from the interface you already use, and set its options.
Install
The SHOTpy wheels include SHOT with the open-source subsolvers Cbc, Ipopt and HiGHS, and need Python 3.11 or later. They are built for Linux (x86-64 and ARM64), macOS (Apple silicon, macOS 14 and later), and Windows x64. The package also installs the SHOT command. On platforms without a matching wheel, building from source needs the compiler and dependencies in the compilation instructions.
pip install SHOTpy
The public wheels do not include CPLEX, Gurobi, GAMS or CONOPT. If one of them is installed and licensed on your computer, build SHOTpy from source with it:
pip install SHOTpy --no-binary SHOTpy \
--config-settings=cmake.define.HAS_GUROBI=on \
--config-settings=cmake.define.GUROBI_DIR=/path/to/gurobiUse the same pattern with HAS_CPLEX and CPLEX_DIR, or HAS_GAMS and GAMS_DIR.
Download the executable archive for Linux x86-64 or ARM64, macOS ARM64, or Windows x64 from GitHub releases. It includes the same open-source solvers as SHOTpy and needs no Python installation. Extract the whole archive and keep its files together. Run ./SHOT on Linux/macOS or SHOT.exe on Windows.
SHOT is included in GAMS, and inside GAMS it can use any licensed NLP solver as subsolver. In GAMSPy, the Python modeling library of GAMS, SHOT is an add-on solver that you install separately. This is the easiest way to use SHOT.
pip install gamspy
gamspy install solver shotTo try SHOT without installing anything, submit a model in GAMS format to SHOT on the NEOS Server, a free internet-based service for numerical optimization.
SHOT compiles on Linux and macOS with CMake and a C++17 compiler, and natively on Windows x64 with MSVC. WSL remains an alternative. It needs a MIP solver; primal NLP searches can use an external NLP solver or SHOT itself. The open-source Cbc, HiGHS and Ipopt provide a complete setup. HiGHS is compiled from its submodule by default.
git clone https://github.com/coin-or/SHOT.git
cd SHOT
git submodule update --init --recursive
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DCBC_DIR=/path/to/cbc \
-DIPOPT_DIR=/path/to/ipopt
cmake --build build --parallelCBC_DIR and IPOPT_DIR give the locations of Cbc and Ipopt, if CMake does not find them itself. To use an installed HiGHS instead of the submodule, add -DUSE_EXTERNAL_HIGHS=on and -DHIGHS_EXTERNAL_DIR=/path/to/highs. This builds the SHOT executable and the libSHOTSolver library in build/; add -DHAS_PYTHON=on to also build SHOTpy. Check the CMake output for lines saying that a solver was not found; such a solver is left out of the build.
SHOT reads AMPL (.nl) and OSiL (.osil) files out of the box. Reading GAMS (.gms) files requires SHOT to be compiled with a GAMS installation.
Solve a problem
import SHOTpy
solver = SHOTpy.Solver()
problem = SHOTpy.Problem(solver)
x = problem.addVariable("x", SHOTpy.VariableType.Real, 0.0, 10.0)
y = problem.addVariable("y", SHOTpy.VariableType.Integer, 0.0, 10.0)
problem.setObjective(SHOTpy.exp(x) + (y - 2.5)**2)
problem.addConstraint(x + y >= 3, "c")
problem.finalize()
solver.setProblem(problem)
solver.solveProblem()
print(solver.getModelReturnStatus())
if solver.hasPrimalSolution():
print(solver.getPrimalSolution().point)Build the problem with the API, or read it from a file with solver.setProblem("problem.nl"), which also accepts .gms and .osil files. The tutorial notebook covers nonlinear expressions, settings and callbacks.
import gamspy as gp
m = gp.Container()
x = gp.Variable(m, name="x", type="integer")
y = gp.Variable(m, name="y", type="positive")
x.up = 10
y.up = 10
con = gp.Equation(m, name="con")
con[...] = gp.math.sqr(x - 4) + gp.math.sqr(y - 3) <= 4
model = gp.Model(m, name="model", equations=[con], problem="MINLP",
sense=gp.Sense.MIN, objective=2 * x + y)
model.solve(solver="SHOT")
print(model.objective_value)Install SHOT in GAMSPy with gamspy install solver shot, then pass solver="SHOT" to solve() for MINLP and MIQCP models.
* Select SHOT for the problem class, in the model or on the command line
option minlp = shot;
solve mymodel using minlp minimizing obj;You can also set it on the command line, e.g., gams model.gms minlp=shot.
import pyomo.environ as pyo
model = pyo.ConcreteModel()
# ... variables, constraints and objective ...
# SHOT is called through the AMPL Solver Library (ASL) interface
solver = pyo.SolverFactory("asl:SHOT") # or add executable="/path/to/SHOT"
solver.solve(model, tee=True)The SHOT executable must be on your path, or given with executable. With GAMS installed, you can also use SHOT through the Pyomo GAMS interface.
using JuMP, AmplNLWriter
model = Model(() -> AmplNLWriter.Optimizer("SHOT")) # or the full path to SHOT
# ... variables, constraints and objective ...
optimize!(model)JuMP calls SHOT through AmplNLWriter.jl, which writes the model in AMPL .nl format. A native JuMP interface is on the roadmap after SHOT 2.0.
# SHOT reads the .nl file and writes problem.sol next to it
./SHOT problem.nl --AMPLSHOT works with any modeling system that uses the AMPL Solver Library: models in .nl format and solutions in .sol format. In AMPL, set SHOT as the solver with option solver.
./SHOT problem.osil # OSiL is always supported
./SHOT problem.nl --timelimit=60 # AMPL
./SHOT problem.gms --mip=highs # GAMS
./SHOT --help # what this build supportsSHOT prints the dual and primal bounds as it iterates, and writes the solution in OSrL format and a log to SHOT.log.
#include "Solver.h"
auto solver = std::make_unique<SHOT::Solver>();
solver->updateSetting("Termination.TimeLimit", 60.0);
if(solver->setProblem("problem.osil") && solver->solveProblem())
{
double primal = solver->getPrimalBound();
double dual = solver->getCurrentDualBound();
}Link against libSHOTSolver to embed SHOT in other software; the console application is a thin wrapper around this API. Callbacks are registered with registerCallback.
Set options
Any setting can be given on the command line as Name=value, without spaces around
the =. Names are case sensitive. Unknown names are ignored, and SHOT lists them in a warning.
./SHOT problem.nl Termination.TimeLimit=100.0 Termination.ObjectiveGap.Relative=0.001
In SHOTpy and C++, use updateSetting before setProblem.
Settings can also be read from a file with --opt [file] (GAMS style) or
--osol [file] (OSoL); without a file name, SHOT reads the default file if it exists, otherwise it writes a template. Common settings have shorthand flags:
| Flag | Effect |
|---|---|
--convex | Assume that the problem is convex |
--mip=<cbc|highs|cplex|gurobi> | Select the MIP solver of the dual strategy |
--nlp=<ipopt|gams|shot|uno|conopt> | Select the NLP solver of the primal strategy |
--tree=<single|multi> | Single-tree (branch and cut, CPLEX and Gurobi) or multi-tree strategy |
--threads=<n> | Maximum number of threads |
--absgap=<value> | Absolute objective gap tolerance |
--relgap=<value> | Relative objective gap tolerance |
--timelimit=<value> | Time limit in seconds |
--debug[=<directory>] | Write intermediate problems and solutions |
All settings
./SHOT --docs writes options.md with every setting, its type,
default value and description, generated from the build you run. It is the authoritative reference for your version.
See the configuration guide for settings precedence, tolerances and logging recipes.
Read the results
Console
Preparation statistics, selected progress rows, and a final solution report. Set Output.Console.Iteration.Detail=0 for all progress rows. Output.Console.LogLevel independently controls message severity, from 0 (trace) to 6 (off).
Log
SHOT.log in the current directory, or --log file.
Solution
Always in OSrL format, problem.osrl or --osrl file. Add --trc for a GAMS trace file and --sol for an AMPL solution file.
Exit code
0 when the run succeeded, whatever the model status; the status is in the result files.
For minimization, the dual bound is a lower bound and the best feasible objective is an upper bound;
for maximization the directions reverse. A relative gap of 0.001 means 0.1%.
An iteration label such as MILP-O describes a subproblem.
Read the final model status to see whether the original problem was solved to global optimality.
#: type time cuts + / total dual / primal abs. / rel. gap current obj. / error
6: MILP-SL1 0.17 2 / 12 0.716245 / 14.9066 14.2 / 0.95 7.92975 / 0.592
1: NLPSOLPT-R 0.19 2 / 12 0.716245 / 10 9.3 / 0.93 10 / 9.61e-9
Here the MIP solve reaches a solution limit, then a fixed-integer NLP call improves the feasible objective to 10.
The NLP counter starts at one. Its final R means the reformulated model.
A * beside a dual bound means it is a working bound without a global guarantee;
use the final report's global bound when reporting results. Check that a primal solution exists before reading its values.
Understand solver output Results and status Command-line reference Debugging SHOT