Lythos LE
Limit equilibrium slope stability analysis in pure Python, with a browser front end.
Lythos LE computes the factor of safety of slopes and embankments with the method of slices, the same class of analysis as Rocscience Slide or GeoStudio SLOPE/W. It searches for the critical slip surface, reports every classical method on it, and draws the section in the browser.
- No dependencies. The solver, the web server and the front end are standard
library and vanilla JavaScript.
python -m lythosle serveworks on a bare Python 3.9+ install with nothing to pip install. - Eight methods, from Fellenius to Morgenstern-Price, all built on one slice formulation so the differences between them are the assumptions, not the code.
- Validated against closed-form solutions, Taylor's stability numbers and the ACADS benchmark problem (see Validation).
Quick start
git clone https://github.com/hdaltuntas/lythosle
cd lythosle
python main.py # browser interface on http://127.0.0.1:8000
python main.py example # list the built-in examples
python main.py example homogeneous # run one and print the report
python -m unittest discover -s tests # run the test suite
Nothing needs installing. main.py takes everything the CLI does and starts
the web interface when given nothing; HOST and PORT override the address,
so a host that sets PORT gets a server bound to every interface. The same
commands are available as python -m lythosle …, and pip install -e . adds a
lythosle command.
What it does
| Methods | Ordinary (Fellenius), Bishop simplified, Janbu simplified and corrected, Corps of Engineers #1, Lowe-Karafiath, Spencer, Morgenstern-Price (half-sine, constant or trapezoidal interslice function) |
| Surfaces | Circular (grid-and-tangent search with adaptive box and refinement), a single specified circle, a user-defined non-circular surface, and non-circular optimisation from the critical circle |
| Strength | Effective stress (c', phi'), undrained (su, optionally increasing linearly with depth), impenetrable and no-strength materials |
| Groundwater | Piezometric water table, per-material pore pressure ratio ru, separate saturated unit weights, ponded water on the slope |
| Loading | Surface surcharges, pseudo-static seismic coefficients kh and kv, reinforcement (nails, anchors, geosynthetics), tension cracks with optional water pressure |
| Output | Factor of safety per method, the critical surface, a full slice force table (CSV), the lambda-FS plot for Spencer/Morgenstern-Price, the search grid, JSON for everything |
The three ways in
Browser
python -m lythosle serve --port 8000 --open
Build the geometry from a template or by typing coordinates, set up materials, groundwater and loading in the sidebar, then Run analysis (or Ctrl/Cmd + Enter). The section view shows the layers, the phreatic surface, the critical surface with its centre of rotation, the search grid coloured by factor of safety, the slices and the reinforcement. Results can be exported as SVG, CSV and JSON. The interface follows the system light/dark setting; the toggle in the header pins it.
?example=layered_water and ?theme=dark work as URL parameters.
The interface follows Claude's design language: the ivory and charcoal
surfaces, the clay accent, sentence-case labels and a serif for the wordmark,
headings and prose. Claude's own faces (Styrene, Tiempos, Copernicus) are
licensed, so the stack asks for them first and falls back to Inter and
Newsreader, which are bundled in lythosle/web/static/fonts/ — nothing is
fetched from a CDN at runtime, and the page looks the same offline.
If you prefer FastAPI, uvicorn lythosle.web.app:app serves exactly the same
API (pip install fastapi uvicorn first).
Command line
lythosle analyze model.json --method bishop --method spencer \
--slices 60 --json result.json --csv slices.csv
lythosle example seismic --optimize
lythosle methods
analyze takes either a bare model file or a {"model": ..., "options": ...}
file — which is exactly what the browser's Download button produces.
Python
from lythosle import SlopeModel, AnalysisOptions, analyze
model = SlopeModel.from_dict({
"profile": [[0, 0], [10, 0], [30, 10], [50, 10]],
"materials": [{"name": "fill", "unit_weight": 20, "cohesion": 3,
"friction_angle": 19.6}],
"layers": [{"material": "fill"}],
})
result = analyze(model, AnalysisOptions.from_dict({
"methods": ["bishop", "spencer"],
"n_slices": 60,
"search": {"nx": 16, "ny": 16, "n_tangent": 16, "refine_passes": 4},
}))
print(result.critical_fs) # 0.985
print(result.results["spencer"].lam) # interslice force ratio
print(result.text_report())
Lower level pieces are available too:
from lythosle import build_slices, circular_surface, solve_all
surface = circular_surface(model.canonical(), xc=20, yc=30, radius=28)
mass = build_slices(model.canonical(), surface, n_slices=50)
print({k: v.fs for k, v in solve_all(mass).items()})
Model format
Coordinates are [x, y] with y as elevation, in whatever consistent unit set
you use (kN/m³ and kPa, or pcf and psf). The full reference is in
docs/model-format.md; the short version:
{
"name": "Layered slope",
"units": "metric",
"profile": [[0, 0], [18, 0], [48, 15], [75, 15]],
"materials": [
{"name": "Fill", "unit_weight": 18, "sat_unit_weight": 19.5,
"cohesion": 5, "friction_angle": 26, "color": "#C4A883"},
{"name": "Clay", "unit_weight": 19, "strength_model": "undrained",
"su": 40, "su_gradient": 1.5, "su_datum": 0}
],
"layers": [
{"material": "Fill"},
{"material": "Clay", "boundary": [[0, -4], [75, 7]]}
],
"water_table": [[0, -2], [30, 3.5], [75, 9.5]],
"seismic": {"kh": 0.15, "kv": 0},
"surcharges": [{"x1": 50, "x2": 70, "pressure": 20}],
"supports": [{"name": "Nail 1", "x1": 14, "y1": 1.5,
"x2": 28, "y2": -0.2, "capacity": 40}],
"tension_crack": {"enabled": true, "depth": 3, "water_fill": 1.0}
}
Layers are listed from the top down. The first one starts at the ground surface; each one below it carries the boundary that forms its top. Boundaries and the water table are extended horizontally beyond their end points.
The slope may be drawn facing either way: the solver mirrors the model
internally so the crest is on the right, and mirrors every result back. Set
"direction": "left" | "right" in the options to analyse a chosen face of a
two-sided embankment.
Formulation
Every method is built on the same slice equations, so the only differences are
which equilibrium conditions are satisfied and what is assumed about the
interslice forces X = lambda * f(x) * E:
| Method | Moment | Force | Interslice shear |
|---|---|---|---|
| Ordinary / Fellenius | yes | no | ignored entirely |
| Bishop simplified | yes | no | X = 0 |
| Janbu simplified / corrected | no | yes | X = 0 (corrected applies Janbu's f0) |
| Corps of Engineers #1 | no | yes | parallel to the entry-exit chord |
| Lowe-Karafiath | no | yes | average of ground and base inclination |
| Spencer | yes | yes | constant inclination, solved for |
| Morgenstern-Price | yes | yes | f(x) shape, lambda solved for |
The base normal force comes from vertical equilibrium of each slice,
N = [ W (1 + kv) + (X_right - X_left) - (c l - u l tan phi) sin(alpha) / F ] / m_alpha
m_alpha = cos(alpha) + sin(alpha) tan(phi) / F
and the factor of safety from moment equilibrium about the centre of rotation
or from horizontal force equilibrium of the whole mass. Spencer and
Morgenstern-Price iterate on lambda until the two agree — the crossing point
you can see on the lambda-FS plot in the interface.
The sign conventions, the treatment of pore pressure, seismic loads, reinforcement, tension cracks, and the search algorithms are documented in docs/theory.md. Read it before trusting a number.
Validation
tests/test_methods.py checks the solver against results that do not come from
this code:
| Check | Reference | Result |
|---|---|---|
| Circular arc in phi = 0 soil | direct integration of c L R / M |
within 0.002 of the closed form; identical across Ordinary, Bishop, Spencer and Morgenstern-Price, as theory requires |
| Long planar surface | infinite slope, FS = tan(phi')/tan(beta) |
within 1% for Bishop, Janbu, Spencer and Morgenstern-Price |
| Toe circles, phi = 0, beta = 53-75 deg | Taylor (1937) stability numbers | within 1.5% |
| ACADS problem 1(a) | published FS = 1.00 | Bishop 0.985, Spencer 0.984 |
| Mirrored geometry | the same slope drawn facing the other way | identical FS and lambda |
| Slice refinement | 20 to 200 slices | converges monotonically, < 0.002 change past 100 slices |
Method relationships are asserted as well: Ordinary is the most conservative,
Bishop sits within 2% of Spencer for circular surfaces, and Morgenstern-Price
needs a larger lambda than Spencer because the half-sine function averages
less than one.
Limitations
Worth knowing before you use a number in anger:
- Two-dimensional analysis only, per unit width out of plane.
- The slice weight uses the mid-ordinate of each slice, so strongly curved boundaries need more slices (the default 50 is enough for typical sections).
- Moment-only methods (Ordinary, Bishop) on a non-circular surface depend on the choice of moment axis. The result is reported with a warning; use Spencer or Morgenstern-Price for non-circular surfaces.
- Spencer and Morgenstern-Price have no solution when
FmandFfnever intersect, which happens when reinforcement is large enough to satisfy force equilibrium by itself. Lythos LE reports this as "no solution" with both values rather than inventing a number. - Reinforcement is applied as a known force at the intersection with the slip surface. Pull-out capacity along the anchored length is not calculated for you — put the design force in.
- Negative effective normal forces are clipped to zero (the usual practice),
and surfaces with a very small
m_alphaare flagged as poorly conditioned. - Probabilistic analysis, rapid drawdown, anisotropic strength and 3D effects are not implemented.
Project layout
main.py run the interface, or anything the CLI does
lythosle/
geometry.py polylines, intersections, polygon helpers
materials.py strength models
model.py geometry, stratigraphy, groundwater, loading, mirroring
slices.py slip surfaces and the slicing of the sliding mass
methods.py the eight limit equilibrium solvers
search.py grid-and-tangent search and non-circular optimisation
analysis.py the driver, reporting and drawing data
examples.py six worked examples
cli.py command line interface
web/ API, standard library server, optional FastAPI app, front end
tests/ 70 tests, ~60 s
docs/ theory, model format, exported example models
Licence
MIT.
Release files for lythosle 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lythosle-0.1.0.tar.gz | 813.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lythosle-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.6 MB
Release files / lythosle-0.1.0.tar.gz
| Download URL | lythosle-0.1.0.tar.gz |
|---|---|
| Size | 813.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4140a394d1d380525967309b9afb8f7ff51a8b3a0cafb6e845e11cb388992ca6
|
|
BLAKE2b-256 checksum How to use checksums |
1196d12f4b86d37a85438086789b3ab6905a3648bb6187cd29d4c248f343d32d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / lythosle-0.1.0-py3-none-any.whl
| Download URL | lythosle-0.1.0-py3-none-any.whl |
|---|---|
| Size | 795.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8fc475af585cfa8a6459ce262b87371105dca823815be4137697e6e8a25d871b
|
|
BLAKE2b-256 checksum How to use checksums |
d55018c5382e88544f9657f73eaa55554d50c4445cea6140e21c437184535b06
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|