Installation¶
Scaly supports Python 3.12 or newer on Linux and macOS. Numerical function calls compile generated C, so the Python package needs access to a C compiler even when the application itself is entirely Python.
The core package and solver plugins¶
The core package provides symbolic expressions, functions, derivatives, and C generation. It can be installed in a uv project or a Python environment:
Optimization solvers are optional packages:
| Solver | uv command | pip command |
|---|---|---|
| PIQP, for convex quadratic problems | uv add "scaly[piqp]" |
pip install "scaly[piqp]" |
| IPOPT, for nonlinear problems | uv add "scaly[ipopt]" |
pip install "scaly[ipopt]" |
| Scaly SQP, for nonlinear problems | uv add "scaly[sqp]" |
pip install "scaly[sqp]" |
| All three | uv add "scaly[solvers]" |
pip install "scaly[solvers]" |
A solver plugin connects the symbolic problem to a native optimization library. The solver wheels include those native libraries. If no wheel matches the platform, a source build needs additional tools, described in Contributing. The backend guide compares the supported problems and solver options.
Compiler selection¶
Scaly normally uses cc from PATH. Apple's command-line developer tools
provide it on macOS, and build-essential provides it on Debian and Ubuntu.
SCALY_CC selects a different compiler. If it is unset, Scaly also respects
CC before falling back to cc.
For example, this selects Clang for one process:
The selected executable must exist. An invalid override does not fall back to the system compiler. Environment variables describes the compiler and cache settings.
Reading the toolchain report¶
scaly_toolchain reports the selected compiler, the compiled-function cache,
the native compilation settings, and the installed solver libraries:
In an environment installed with pip, the command is scaly_toolchain.
An excerpt from a Linux installation with IPOPT available looks like this.
Paths are shortened, and other installed solvers are omitted:
Scaly native toolchain
cache root: /home/user/.cache/scaly/jit
cc: /usr/bin/cc (PATH)
native build recipe:
/* Scaly build recipe
* CPU baseline: host-local native CPU
* lanes=8, dialect=gnu, vector_libm=glibc, reciprocal=False
* Math library: glibc x86-64; tanh requires glibc >= 2.35
* gcc -O3 -march=native -fno-math-errno -c module.c
* clang -O3 -march=native -fno-math-errno -c module.c
* Link with: -lmvec -lm
*/
solver source: plugin
include dirs: .../scaly_ipopt/include, ...
lib dirs: .../scaly_ipopt/lib, ...
ipopt: .../scaly_ipopt/lib/libipopt.so
ipopt discoverable: True
ipopt loadable: True
...
JIT solver flags: -I... -L... -Wl,-rpath,... -lipopt ...
The native build recipe describes the detected CPU and vector math settings.
Its gcc and clang lines are the suggested commands for building exported
code. Numerical evaluation from Python uses the same CPU and math flags but
compiles with SCALY_CC_OPT, -O2 by default, as described in
environment variables.
Lane width and library selection vary by machine. The
code generation guide explains these choices and how to select
them for exported code.
discoverable means Scaly found the library and its C headers. loadable
also checks that the operating system can load the library. A library can be
present but fail to load because one of its native dependencies is unavailable.
The final line reports the include and link flags used for solver compilation,
not all optimization flags used to compile a model.
A cc: <missing> line prevents numerical evaluation of any function. A missing
solver library only affects functions that use that solver. For an installed
plugin, failure to locate or load its native library raises
sc.SolverLibraryError.
Missing compiler and solver-library problems can therefore be diagnosed
separately.
A numerical check¶
Importing scaly alone does not check C compilation. Evaluating this function checks the numerical path as well:
import numpy as np
import scaly as sc
@sc.function(sc.L("x", 3), sc.L("energy", ...))
def energy(x: sc.Expr) -> sc.Expr:
return sc.sumsqr(x)
value = energy(np.array([1.0, 2.0, 3.0]))
np.testing.assert_allclose(value, 14.0)
print(value)
# 14.0
This example needs no solver plugin. Getting started introduces the symbolic model and adds an optimization solve.