lythosle 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. lythosle-0.1.0/LICENSE +21 -0
  2. lythosle-0.1.0/MANIFEST.in +8 -0
  3. lythosle-0.1.0/PKG-INFO +291 -0
  4. lythosle-0.1.0/README.md +257 -0
  5. lythosle-0.1.0/docs/examples/homogeneous.json +56 -0
  6. lythosle-0.1.0/docs/examples/layered_water.json +129 -0
  7. lythosle-0.1.0/docs/examples/reinforced.json +108 -0
  8. lythosle-0.1.0/docs/examples/seismic.json +115 -0
  9. lythosle-0.1.0/docs/examples/soft_foundation.json +117 -0
  10. lythosle-0.1.0/docs/examples/tension_crack.json +82 -0
  11. lythosle-0.1.0/docs/model-format.md +125 -0
  12. lythosle-0.1.0/docs/theory.md +225 -0
  13. lythosle-0.1.0/lythosle/__init__.py +35 -0
  14. lythosle-0.1.0/lythosle/__main__.py +3 -0
  15. lythosle-0.1.0/lythosle/analysis.py +429 -0
  16. lythosle-0.1.0/lythosle/cli.py +173 -0
  17. lythosle-0.1.0/lythosle/examples.py +208 -0
  18. lythosle-0.1.0/lythosle/geometry.py +241 -0
  19. lythosle-0.1.0/lythosle/materials.py +135 -0
  20. lythosle-0.1.0/lythosle/methods.py +582 -0
  21. lythosle-0.1.0/lythosle/model.py +431 -0
  22. lythosle-0.1.0/lythosle/search.py +463 -0
  23. lythosle-0.1.0/lythosle/slices.py +369 -0
  24. lythosle-0.1.0/lythosle/web/__init__.py +10 -0
  25. lythosle-0.1.0/lythosle/web/api.py +108 -0
  26. lythosle-0.1.0/lythosle/web/app.py +69 -0
  27. lythosle-0.1.0/lythosle/web/server.py +125 -0
  28. lythosle-0.1.0/lythosle/web/static/app.js +991 -0
  29. lythosle-0.1.0/lythosle/web/static/fonts/inter-400-latin-ext.woff2 +0 -0
  30. lythosle-0.1.0/lythosle/web/static/fonts/inter-400-latin.woff2 +0 -0
  31. lythosle-0.1.0/lythosle/web/static/fonts/inter-500-latin-ext.woff2 +0 -0
  32. lythosle-0.1.0/lythosle/web/static/fonts/inter-500-latin.woff2 +0 -0
  33. lythosle-0.1.0/lythosle/web/static/fonts/inter-600-latin-ext.woff2 +0 -0
  34. lythosle-0.1.0/lythosle/web/static/fonts/inter-600-latin.woff2 +0 -0
  35. lythosle-0.1.0/lythosle/web/static/fonts/newsreader-400-latin-ext.woff2 +0 -0
  36. lythosle-0.1.0/lythosle/web/static/fonts/newsreader-400-latin.woff2 +0 -0
  37. lythosle-0.1.0/lythosle/web/static/fonts/newsreader-400i-latin-ext.woff2 +0 -0
  38. lythosle-0.1.0/lythosle/web/static/fonts/newsreader-400i-latin.woff2 +0 -0
  39. lythosle-0.1.0/lythosle/web/static/fonts/newsreader-500-latin-ext.woff2 +0 -0
  40. lythosle-0.1.0/lythosle/web/static/fonts/newsreader-500-latin.woff2 +0 -0
  41. lythosle-0.1.0/lythosle/web/static/fonts/newsreader-600-latin-ext.woff2 +0 -0
  42. lythosle-0.1.0/lythosle/web/static/fonts/newsreader-600-latin.woff2 +0 -0
  43. lythosle-0.1.0/lythosle/web/static/fonts.css +125 -0
  44. lythosle-0.1.0/lythosle/web/static/index.html +271 -0
  45. lythosle-0.1.0/lythosle/web/static/styles.css +456 -0
  46. lythosle-0.1.0/lythosle.egg-info/PKG-INFO +291 -0
  47. lythosle-0.1.0/lythosle.egg-info/SOURCES.txt +59 -0
  48. lythosle-0.1.0/lythosle.egg-info/dependency_links.txt +1 -0
  49. lythosle-0.1.0/lythosle.egg-info/entry_points.txt +2 -0
  50. lythosle-0.1.0/lythosle.egg-info/requires.txt +4 -0
  51. lythosle-0.1.0/lythosle.egg-info/top_level.txt +1 -0
  52. lythosle-0.1.0/main.py +57 -0
  53. lythosle-0.1.0/pyproject.toml +54 -0
  54. lythosle-0.1.0/setup.cfg +4 -0
  55. lythosle-0.1.0/tests/__init__.py +0 -0
  56. lythosle-0.1.0/tests/test_examples.py +55 -0
  57. lythosle-0.1.0/tests/test_geometry.py +87 -0
  58. lythosle-0.1.0/tests/test_methods.py +229 -0
  59. lythosle-0.1.0/tests/test_model.py +85 -0
  60. lythosle-0.1.0/tests/test_search.py +166 -0
  61. lythosle-0.1.0/tests/test_web.py +171 -0
lythosle-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lythosle contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,8 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ include main.py
5
+ recursive-include lythosle/web/static *
6
+ recursive-include tests *.py
7
+ recursive-include docs *.md *.json
8
+ global-exclude __pycache__ *.py[cod] .DS_Store
@@ -0,0 +1,291 @@
1
+ Metadata-Version: 2.4
2
+ Name: lythosle
3
+ Version: 0.1.0
4
+ Summary: Limit equilibrium slope stability analysis with a browser front end
5
+ Author-email: Hasan Deniz Altuntaş <lythosfea@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hdaltuntas/lythosle
8
+ Project-URL: Source, https://github.com/hdaltuntas/lythosle
9
+ Project-URL: Repository, https://github.com/hdaltuntas/lythosle
10
+ Project-URL: Issues, https://github.com/hdaltuntas/lythosle/issues
11
+ Project-URL: Documentation, https://github.com/hdaltuntas/lythosle/blob/main/docs/theory.md
12
+ Project-URL: Validation, https://github.com/hdaltuntas/lythosle/blob/main/README.md#validation
13
+ Keywords: geotechnical,slope stability,limit equilibrium,factor of safety,bishop,janbu,spencer,morgenstern-price,method of slices,civil engineering,geotechnics
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Intended Audience :: Education
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering
25
+ Classifier: Topic :: Scientific/Engineering :: Physics
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Provides-Extra: fastapi
31
+ Requires-Dist: fastapi>=0.100; extra == "fastapi"
32
+ Requires-Dist: uvicorn>=0.23; extra == "fastapi"
33
+ Dynamic: license-file
34
+
35
+ # Lythos LE
36
+
37
+ Limit equilibrium slope stability analysis in pure Python, with a browser front end.
38
+
39
+ Lythos LE computes the factor of safety of slopes and embankments with the method
40
+ of slices, the same class of analysis as Rocscience Slide or GeoStudio SLOPE/W.
41
+ It searches for the critical slip surface, reports every classical method on it,
42
+ and draws the section in the browser.
43
+
44
+ * **No dependencies.** The solver, the web server and the front end are standard
45
+ library and vanilla JavaScript. `python -m lythosle serve` works on a bare
46
+ Python 3.9+ install with nothing to pip install.
47
+ * **Eight methods**, from Fellenius to Morgenstern-Price, all built on one slice
48
+ formulation so the differences between them are the assumptions, not the code.
49
+ * **Validated** against closed-form solutions, Taylor's stability numbers and the
50
+ ACADS benchmark problem (see [Validation](#validation)).
51
+
52
+ ![the browser interface](docs/screenshot-light.png)
53
+
54
+ ---
55
+
56
+ ## Quick start
57
+
58
+ ```bash
59
+ git clone https://github.com/hdaltuntas/lythosle
60
+ cd lythosle
61
+
62
+ python main.py # browser interface on http://127.0.0.1:8000
63
+ python main.py example # list the built-in examples
64
+ python main.py example homogeneous # run one and print the report
65
+ python -m unittest discover -s tests # run the test suite
66
+ ```
67
+
68
+ Nothing needs installing. `main.py` takes everything the CLI does and starts
69
+ the web interface when given nothing; `HOST` and `PORT` override the address,
70
+ so a host that sets `PORT` gets a server bound to every interface. The same
71
+ commands are available as `python -m lythosle …`, and `pip install -e .` adds a
72
+ `lythosle` command.
73
+
74
+ ## What it does
75
+
76
+ | | |
77
+ |---|---|
78
+ | **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) |
79
+ | **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 |
80
+ | **Strength** | Effective stress (c', phi'), undrained (s<sub>u</sub>, optionally increasing linearly with depth), impenetrable and no-strength materials |
81
+ | **Groundwater** | Piezometric water table, per-material pore pressure ratio r<sub>u</sub>, separate saturated unit weights, ponded water on the slope |
82
+ | **Loading** | Surface surcharges, pseudo-static seismic coefficients k<sub>h</sub> and k<sub>v</sub>, reinforcement (nails, anchors, geosynthetics), tension cracks with optional water pressure |
83
+ | **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 |
84
+
85
+ ## The three ways in
86
+
87
+ ### Browser
88
+
89
+ ```bash
90
+ python -m lythosle serve --port 8000 --open
91
+ ```
92
+
93
+ Build the geometry from a template or by typing coordinates, set up materials,
94
+ groundwater and loading in the sidebar, then **Run analysis** (or Ctrl/Cmd +
95
+ Enter). The section view shows the layers, the phreatic surface, the critical
96
+ surface with its centre of rotation, the search grid coloured by factor of
97
+ safety, the slices and the reinforcement. Results can be exported as SVG, CSV
98
+ and JSON. The interface follows the system light/dark setting; the toggle in
99
+ the header pins it.
100
+
101
+ `?example=layered_water` and `?theme=dark` work as URL parameters.
102
+
103
+ The interface follows Claude's design language: the ivory and charcoal
104
+ surfaces, the clay accent, sentence-case labels and a serif for the wordmark,
105
+ headings and prose. Claude's own faces (Styrene, Tiempos, Copernicus) are
106
+ licensed, so the stack asks for them first and falls back to Inter and
107
+ Newsreader, which are bundled in `lythosle/web/static/fonts/` — nothing is
108
+ fetched from a CDN at runtime, and the page looks the same offline.
109
+
110
+ If you prefer FastAPI, `uvicorn lythosle.web.app:app` serves exactly the same
111
+ API (`pip install fastapi uvicorn` first).
112
+
113
+ ### Command line
114
+
115
+ ```bash
116
+ lythosle analyze model.json --method bishop --method spencer \
117
+ --slices 60 --json result.json --csv slices.csv
118
+ lythosle example seismic --optimize
119
+ lythosle methods
120
+ ```
121
+
122
+ `analyze` takes either a bare model file or a `{"model": ..., "options": ...}`
123
+ file — which is exactly what the browser's **Download** button produces.
124
+
125
+ ### Python
126
+
127
+ ```python
128
+ from lythosle import SlopeModel, AnalysisOptions, analyze
129
+
130
+ model = SlopeModel.from_dict({
131
+ "profile": [[0, 0], [10, 0], [30, 10], [50, 10]],
132
+ "materials": [{"name": "fill", "unit_weight": 20, "cohesion": 3,
133
+ "friction_angle": 19.6}],
134
+ "layers": [{"material": "fill"}],
135
+ })
136
+
137
+ result = analyze(model, AnalysisOptions.from_dict({
138
+ "methods": ["bishop", "spencer"],
139
+ "n_slices": 60,
140
+ "search": {"nx": 16, "ny": 16, "n_tangent": 16, "refine_passes": 4},
141
+ }))
142
+
143
+ print(result.critical_fs) # 0.985
144
+ print(result.results["spencer"].lam) # interslice force ratio
145
+ print(result.text_report())
146
+ ```
147
+
148
+ Lower level pieces are available too:
149
+
150
+ ```python
151
+ from lythosle import build_slices, circular_surface, solve_all
152
+
153
+ surface = circular_surface(model.canonical(), xc=20, yc=30, radius=28)
154
+ mass = build_slices(model.canonical(), surface, n_slices=50)
155
+ print({k: v.fs for k, v in solve_all(mass).items()})
156
+ ```
157
+
158
+ ## Model format
159
+
160
+ Coordinates are `[x, y]` with `y` as elevation, in whatever consistent unit set
161
+ you use (kN/m³ and kPa, or pcf and psf). The full reference is in
162
+ [docs/model-format.md](docs/model-format.md); the short version:
163
+
164
+ ```json
165
+ {
166
+ "name": "Layered slope",
167
+ "units": "metric",
168
+ "profile": [[0, 0], [18, 0], [48, 15], [75, 15]],
169
+ "materials": [
170
+ {"name": "Fill", "unit_weight": 18, "sat_unit_weight": 19.5,
171
+ "cohesion": 5, "friction_angle": 26, "color": "#C4A883"},
172
+ {"name": "Clay", "unit_weight": 19, "strength_model": "undrained",
173
+ "su": 40, "su_gradient": 1.5, "su_datum": 0}
174
+ ],
175
+ "layers": [
176
+ {"material": "Fill"},
177
+ {"material": "Clay", "boundary": [[0, -4], [75, 7]]}
178
+ ],
179
+ "water_table": [[0, -2], [30, 3.5], [75, 9.5]],
180
+ "seismic": {"kh": 0.15, "kv": 0},
181
+ "surcharges": [{"x1": 50, "x2": 70, "pressure": 20}],
182
+ "supports": [{"name": "Nail 1", "x1": 14, "y1": 1.5,
183
+ "x2": 28, "y2": -0.2, "capacity": 40}],
184
+ "tension_crack": {"enabled": true, "depth": 3, "water_fill": 1.0}
185
+ }
186
+ ```
187
+
188
+ Layers are listed from the top down. The first one starts at the ground
189
+ surface; each one below it carries the boundary that forms its top. Boundaries
190
+ and the water table are extended horizontally beyond their end points.
191
+
192
+ The slope may be drawn facing either way: the solver mirrors the model
193
+ internally so the crest is on the right, and mirrors every result back. Set
194
+ `"direction": "left" | "right"` in the options to analyse a chosen face of a
195
+ two-sided embankment.
196
+
197
+ ## Formulation
198
+
199
+ Every method is built on the same slice equations, so the only differences are
200
+ which equilibrium conditions are satisfied and what is assumed about the
201
+ interslice forces `X = lambda * f(x) * E`:
202
+
203
+ | Method | Moment | Force | Interslice shear |
204
+ |---|---|---|---|
205
+ | Ordinary / Fellenius | yes | no | ignored entirely |
206
+ | Bishop simplified | yes | no | `X = 0` |
207
+ | Janbu simplified / corrected | no | yes | `X = 0` (corrected applies Janbu's `f0`) |
208
+ | Corps of Engineers #1 | no | yes | parallel to the entry-exit chord |
209
+ | Lowe-Karafiath | no | yes | average of ground and base inclination |
210
+ | Spencer | yes | yes | constant inclination, solved for |
211
+ | Morgenstern-Price | yes | yes | `f(x)` shape, `lambda` solved for |
212
+
213
+ The base normal force comes from vertical equilibrium of each slice,
214
+
215
+ ```
216
+ N = [ W (1 + kv) + (X_right - X_left) - (c l - u l tan phi) sin(alpha) / F ] / m_alpha
217
+ m_alpha = cos(alpha) + sin(alpha) tan(phi) / F
218
+ ```
219
+
220
+ and the factor of safety from moment equilibrium about the centre of rotation
221
+ or from horizontal force equilibrium of the whole mass. Spencer and
222
+ Morgenstern-Price iterate on `lambda` until the two agree — the crossing point
223
+ you can see on the lambda-FS plot in the interface.
224
+
225
+ The sign conventions, the treatment of pore pressure, seismic loads,
226
+ reinforcement, tension cracks, and the search algorithms are documented in
227
+ [docs/theory.md](docs/theory.md). Read it before trusting a number.
228
+
229
+ ## Validation
230
+
231
+ `tests/test_methods.py` checks the solver against results that do not come from
232
+ this code:
233
+
234
+ | Check | Reference | Result |
235
+ |---|---|---|
236
+ | 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 |
237
+ | Long planar surface | infinite slope, `FS = tan(phi')/tan(beta)` | within 1% for Bishop, Janbu, Spencer and Morgenstern-Price |
238
+ | Toe circles, phi = 0, beta = 53-75 deg | Taylor (1937) stability numbers | within 1.5% |
239
+ | ACADS problem 1(a) | published FS = 1.00 | Bishop 0.985, Spencer 0.984 |
240
+ | Mirrored geometry | the same slope drawn facing the other way | identical FS and lambda |
241
+ | Slice refinement | 20 to 200 slices | converges monotonically, < 0.002 change past 100 slices |
242
+
243
+ Method relationships are asserted as well: Ordinary is the most conservative,
244
+ Bishop sits within 2% of Spencer for circular surfaces, and Morgenstern-Price
245
+ needs a larger `lambda` than Spencer because the half-sine function averages
246
+ less than one.
247
+
248
+ ## Limitations
249
+
250
+ Worth knowing before you use a number in anger:
251
+
252
+ * Two-dimensional analysis only, per unit width out of plane.
253
+ * The slice weight uses the mid-ordinate of each slice, so strongly curved
254
+ boundaries need more slices (the default 50 is enough for typical sections).
255
+ * Moment-only methods (Ordinary, Bishop) on a **non-circular** surface depend on
256
+ the choice of moment axis. The result is reported with a warning; use Spencer
257
+ or Morgenstern-Price for non-circular surfaces.
258
+ * Spencer and Morgenstern-Price have no solution when `Fm` and `Ff` never
259
+ intersect, which happens when reinforcement is large enough to satisfy force
260
+ equilibrium by itself. Lythos LE reports this as "no solution" with both
261
+ values rather than inventing a number.
262
+ * Reinforcement is applied as a known force at the intersection with the slip
263
+ surface. Pull-out capacity along the anchored length is not calculated for
264
+ you — put the design force in.
265
+ * Negative effective normal forces are clipped to zero (the usual practice),
266
+ and surfaces with a very small `m_alpha` are flagged as poorly conditioned.
267
+ * Probabilistic analysis, rapid drawdown, anisotropic strength and 3D effects
268
+ are not implemented.
269
+
270
+ ## Project layout
271
+
272
+ ```
273
+ main.py run the interface, or anything the CLI does
274
+ lythosle/
275
+ geometry.py polylines, intersections, polygon helpers
276
+ materials.py strength models
277
+ model.py geometry, stratigraphy, groundwater, loading, mirroring
278
+ slices.py slip surfaces and the slicing of the sliding mass
279
+ methods.py the eight limit equilibrium solvers
280
+ search.py grid-and-tangent search and non-circular optimisation
281
+ analysis.py the driver, reporting and drawing data
282
+ examples.py six worked examples
283
+ cli.py command line interface
284
+ web/ API, standard library server, optional FastAPI app, front end
285
+ tests/ 70 tests, ~60 s
286
+ docs/ theory, model format, exported example models
287
+ ```
288
+
289
+ ## Licence
290
+
291
+ MIT.
@@ -0,0 +1,257 @@
1
+ # Lythos LE
2
+
3
+ Limit equilibrium slope stability analysis in pure Python, with a browser front end.
4
+
5
+ Lythos LE computes the factor of safety of slopes and embankments with the method
6
+ of slices, the same class of analysis as Rocscience Slide or GeoStudio SLOPE/W.
7
+ It searches for the critical slip surface, reports every classical method on it,
8
+ and draws the section in the browser.
9
+
10
+ * **No dependencies.** The solver, the web server and the front end are standard
11
+ library and vanilla JavaScript. `python -m lythosle serve` works on a bare
12
+ Python 3.9+ install with nothing to pip install.
13
+ * **Eight methods**, from Fellenius to Morgenstern-Price, all built on one slice
14
+ formulation so the differences between them are the assumptions, not the code.
15
+ * **Validated** against closed-form solutions, Taylor's stability numbers and the
16
+ ACADS benchmark problem (see [Validation](#validation)).
17
+
18
+ ![the browser interface](docs/screenshot-light.png)
19
+
20
+ ---
21
+
22
+ ## Quick start
23
+
24
+ ```bash
25
+ git clone https://github.com/hdaltuntas/lythosle
26
+ cd lythosle
27
+
28
+ python main.py # browser interface on http://127.0.0.1:8000
29
+ python main.py example # list the built-in examples
30
+ python main.py example homogeneous # run one and print the report
31
+ python -m unittest discover -s tests # run the test suite
32
+ ```
33
+
34
+ Nothing needs installing. `main.py` takes everything the CLI does and starts
35
+ the web interface when given nothing; `HOST` and `PORT` override the address,
36
+ so a host that sets `PORT` gets a server bound to every interface. The same
37
+ commands are available as `python -m lythosle …`, and `pip install -e .` adds a
38
+ `lythosle` command.
39
+
40
+ ## What it does
41
+
42
+ | | |
43
+ |---|---|
44
+ | **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) |
45
+ | **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 |
46
+ | **Strength** | Effective stress (c', phi'), undrained (s<sub>u</sub>, optionally increasing linearly with depth), impenetrable and no-strength materials |
47
+ | **Groundwater** | Piezometric water table, per-material pore pressure ratio r<sub>u</sub>, separate saturated unit weights, ponded water on the slope |
48
+ | **Loading** | Surface surcharges, pseudo-static seismic coefficients k<sub>h</sub> and k<sub>v</sub>, reinforcement (nails, anchors, geosynthetics), tension cracks with optional water pressure |
49
+ | **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 |
50
+
51
+ ## The three ways in
52
+
53
+ ### Browser
54
+
55
+ ```bash
56
+ python -m lythosle serve --port 8000 --open
57
+ ```
58
+
59
+ Build the geometry from a template or by typing coordinates, set up materials,
60
+ groundwater and loading in the sidebar, then **Run analysis** (or Ctrl/Cmd +
61
+ Enter). The section view shows the layers, the phreatic surface, the critical
62
+ surface with its centre of rotation, the search grid coloured by factor of
63
+ safety, the slices and the reinforcement. Results can be exported as SVG, CSV
64
+ and JSON. The interface follows the system light/dark setting; the toggle in
65
+ the header pins it.
66
+
67
+ `?example=layered_water` and `?theme=dark` work as URL parameters.
68
+
69
+ The interface follows Claude's design language: the ivory and charcoal
70
+ surfaces, the clay accent, sentence-case labels and a serif for the wordmark,
71
+ headings and prose. Claude's own faces (Styrene, Tiempos, Copernicus) are
72
+ licensed, so the stack asks for them first and falls back to Inter and
73
+ Newsreader, which are bundled in `lythosle/web/static/fonts/` — nothing is
74
+ fetched from a CDN at runtime, and the page looks the same offline.
75
+
76
+ If you prefer FastAPI, `uvicorn lythosle.web.app:app` serves exactly the same
77
+ API (`pip install fastapi uvicorn` first).
78
+
79
+ ### Command line
80
+
81
+ ```bash
82
+ lythosle analyze model.json --method bishop --method spencer \
83
+ --slices 60 --json result.json --csv slices.csv
84
+ lythosle example seismic --optimize
85
+ lythosle methods
86
+ ```
87
+
88
+ `analyze` takes either a bare model file or a `{"model": ..., "options": ...}`
89
+ file — which is exactly what the browser's **Download** button produces.
90
+
91
+ ### Python
92
+
93
+ ```python
94
+ from lythosle import SlopeModel, AnalysisOptions, analyze
95
+
96
+ model = SlopeModel.from_dict({
97
+ "profile": [[0, 0], [10, 0], [30, 10], [50, 10]],
98
+ "materials": [{"name": "fill", "unit_weight": 20, "cohesion": 3,
99
+ "friction_angle": 19.6}],
100
+ "layers": [{"material": "fill"}],
101
+ })
102
+
103
+ result = analyze(model, AnalysisOptions.from_dict({
104
+ "methods": ["bishop", "spencer"],
105
+ "n_slices": 60,
106
+ "search": {"nx": 16, "ny": 16, "n_tangent": 16, "refine_passes": 4},
107
+ }))
108
+
109
+ print(result.critical_fs) # 0.985
110
+ print(result.results["spencer"].lam) # interslice force ratio
111
+ print(result.text_report())
112
+ ```
113
+
114
+ Lower level pieces are available too:
115
+
116
+ ```python
117
+ from lythosle import build_slices, circular_surface, solve_all
118
+
119
+ surface = circular_surface(model.canonical(), xc=20, yc=30, radius=28)
120
+ mass = build_slices(model.canonical(), surface, n_slices=50)
121
+ print({k: v.fs for k, v in solve_all(mass).items()})
122
+ ```
123
+
124
+ ## Model format
125
+
126
+ Coordinates are `[x, y]` with `y` as elevation, in whatever consistent unit set
127
+ you use (kN/m³ and kPa, or pcf and psf). The full reference is in
128
+ [docs/model-format.md](docs/model-format.md); the short version:
129
+
130
+ ```json
131
+ {
132
+ "name": "Layered slope",
133
+ "units": "metric",
134
+ "profile": [[0, 0], [18, 0], [48, 15], [75, 15]],
135
+ "materials": [
136
+ {"name": "Fill", "unit_weight": 18, "sat_unit_weight": 19.5,
137
+ "cohesion": 5, "friction_angle": 26, "color": "#C4A883"},
138
+ {"name": "Clay", "unit_weight": 19, "strength_model": "undrained",
139
+ "su": 40, "su_gradient": 1.5, "su_datum": 0}
140
+ ],
141
+ "layers": [
142
+ {"material": "Fill"},
143
+ {"material": "Clay", "boundary": [[0, -4], [75, 7]]}
144
+ ],
145
+ "water_table": [[0, -2], [30, 3.5], [75, 9.5]],
146
+ "seismic": {"kh": 0.15, "kv": 0},
147
+ "surcharges": [{"x1": 50, "x2": 70, "pressure": 20}],
148
+ "supports": [{"name": "Nail 1", "x1": 14, "y1": 1.5,
149
+ "x2": 28, "y2": -0.2, "capacity": 40}],
150
+ "tension_crack": {"enabled": true, "depth": 3, "water_fill": 1.0}
151
+ }
152
+ ```
153
+
154
+ Layers are listed from the top down. The first one starts at the ground
155
+ surface; each one below it carries the boundary that forms its top. Boundaries
156
+ and the water table are extended horizontally beyond their end points.
157
+
158
+ The slope may be drawn facing either way: the solver mirrors the model
159
+ internally so the crest is on the right, and mirrors every result back. Set
160
+ `"direction": "left" | "right"` in the options to analyse a chosen face of a
161
+ two-sided embankment.
162
+
163
+ ## Formulation
164
+
165
+ Every method is built on the same slice equations, so the only differences are
166
+ which equilibrium conditions are satisfied and what is assumed about the
167
+ interslice forces `X = lambda * f(x) * E`:
168
+
169
+ | Method | Moment | Force | Interslice shear |
170
+ |---|---|---|---|
171
+ | Ordinary / Fellenius | yes | no | ignored entirely |
172
+ | Bishop simplified | yes | no | `X = 0` |
173
+ | Janbu simplified / corrected | no | yes | `X = 0` (corrected applies Janbu's `f0`) |
174
+ | Corps of Engineers #1 | no | yes | parallel to the entry-exit chord |
175
+ | Lowe-Karafiath | no | yes | average of ground and base inclination |
176
+ | Spencer | yes | yes | constant inclination, solved for |
177
+ | Morgenstern-Price | yes | yes | `f(x)` shape, `lambda` solved for |
178
+
179
+ The base normal force comes from vertical equilibrium of each slice,
180
+
181
+ ```
182
+ N = [ W (1 + kv) + (X_right - X_left) - (c l - u l tan phi) sin(alpha) / F ] / m_alpha
183
+ m_alpha = cos(alpha) + sin(alpha) tan(phi) / F
184
+ ```
185
+
186
+ and the factor of safety from moment equilibrium about the centre of rotation
187
+ or from horizontal force equilibrium of the whole mass. Spencer and
188
+ Morgenstern-Price iterate on `lambda` until the two agree — the crossing point
189
+ you can see on the lambda-FS plot in the interface.
190
+
191
+ The sign conventions, the treatment of pore pressure, seismic loads,
192
+ reinforcement, tension cracks, and the search algorithms are documented in
193
+ [docs/theory.md](docs/theory.md). Read it before trusting a number.
194
+
195
+ ## Validation
196
+
197
+ `tests/test_methods.py` checks the solver against results that do not come from
198
+ this code:
199
+
200
+ | Check | Reference | Result |
201
+ |---|---|---|
202
+ | 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 |
203
+ | Long planar surface | infinite slope, `FS = tan(phi')/tan(beta)` | within 1% for Bishop, Janbu, Spencer and Morgenstern-Price |
204
+ | Toe circles, phi = 0, beta = 53-75 deg | Taylor (1937) stability numbers | within 1.5% |
205
+ | ACADS problem 1(a) | published FS = 1.00 | Bishop 0.985, Spencer 0.984 |
206
+ | Mirrored geometry | the same slope drawn facing the other way | identical FS and lambda |
207
+ | Slice refinement | 20 to 200 slices | converges monotonically, < 0.002 change past 100 slices |
208
+
209
+ Method relationships are asserted as well: Ordinary is the most conservative,
210
+ Bishop sits within 2% of Spencer for circular surfaces, and Morgenstern-Price
211
+ needs a larger `lambda` than Spencer because the half-sine function averages
212
+ less than one.
213
+
214
+ ## Limitations
215
+
216
+ Worth knowing before you use a number in anger:
217
+
218
+ * Two-dimensional analysis only, per unit width out of plane.
219
+ * The slice weight uses the mid-ordinate of each slice, so strongly curved
220
+ boundaries need more slices (the default 50 is enough for typical sections).
221
+ * Moment-only methods (Ordinary, Bishop) on a **non-circular** surface depend on
222
+ the choice of moment axis. The result is reported with a warning; use Spencer
223
+ or Morgenstern-Price for non-circular surfaces.
224
+ * Spencer and Morgenstern-Price have no solution when `Fm` and `Ff` never
225
+ intersect, which happens when reinforcement is large enough to satisfy force
226
+ equilibrium by itself. Lythos LE reports this as "no solution" with both
227
+ values rather than inventing a number.
228
+ * Reinforcement is applied as a known force at the intersection with the slip
229
+ surface. Pull-out capacity along the anchored length is not calculated for
230
+ you — put the design force in.
231
+ * Negative effective normal forces are clipped to zero (the usual practice),
232
+ and surfaces with a very small `m_alpha` are flagged as poorly conditioned.
233
+ * Probabilistic analysis, rapid drawdown, anisotropic strength and 3D effects
234
+ are not implemented.
235
+
236
+ ## Project layout
237
+
238
+ ```
239
+ main.py run the interface, or anything the CLI does
240
+ lythosle/
241
+ geometry.py polylines, intersections, polygon helpers
242
+ materials.py strength models
243
+ model.py geometry, stratigraphy, groundwater, loading, mirroring
244
+ slices.py slip surfaces and the slicing of the sliding mass
245
+ methods.py the eight limit equilibrium solvers
246
+ search.py grid-and-tangent search and non-circular optimisation
247
+ analysis.py the driver, reporting and drawing data
248
+ examples.py six worked examples
249
+ cli.py command line interface
250
+ web/ API, standard library server, optional FastAPI app, front end
251
+ tests/ 70 tests, ~60 s
252
+ docs/ theory, model format, exported example models
253
+ ```
254
+
255
+ ## Licence
256
+
257
+ MIT.
@@ -0,0 +1,56 @@
1
+ {
2
+ "model": {
3
+ "name": "Homogeneous slope",
4
+ "units": "metric",
5
+ "profile": [
6
+ [
7
+ 0,
8
+ 0
9
+ ],
10
+ [
11
+ 10,
12
+ 0
13
+ ],
14
+ [
15
+ 30.0,
16
+ 10
17
+ ],
18
+ [
19
+ 50.0,
20
+ 10
21
+ ]
22
+ ],
23
+ "materials": [
24
+ {
25
+ "name": "Embankment fill",
26
+ "unit_weight": 20.0,
27
+ "cohesion": 3.0,
28
+ "friction_angle": 19.6,
29
+ "color": "#C4A883"
30
+ }
31
+ ],
32
+ "layers": [
33
+ {
34
+ "material": "Embankment fill"
35
+ }
36
+ ]
37
+ },
38
+ "options": {
39
+ "methods": [
40
+ "ordinary",
41
+ "bishop",
42
+ "janbu_corrected",
43
+ "spencer",
44
+ "morgenstern_price"
45
+ ],
46
+ "n_slices": 50,
47
+ "search": {
48
+ "mode": "auto",
49
+ "method": "bishop",
50
+ "nx": 14,
51
+ "ny": 14,
52
+ "n_tangent": 14,
53
+ "refine_passes": 3
54
+ }
55
+ }
56
+ }