lythossettle 0.1.0__py3-none-any.whl
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.
- lythossettle/__init__.py +45 -0
- lythossettle/__main__.py +6 -0
- lythossettle/cli.py +137 -0
- lythossettle/config.py +116 -0
- lythossettle/consolidation.py +100 -0
- lythossettle/engine.py +619 -0
- lythossettle/forms.py +418 -0
- lythossettle/i18n.py +374 -0
- lythossettle/pdf.py +322 -0
- lythossettle/plot_style.py +61 -0
- lythossettle/plotting.py +367 -0
- lythossettle/render.py +77 -0
- lythossettle/report.py +609 -0
- lythossettle/stress.py +317 -0
- lythossettle/study.py +364 -0
- lythossettle/study_plots.py +208 -0
- lythossettle/summary.py +181 -0
- lythossettle/web/__init__.py +4 -0
- lythossettle/web/server.py +201 -0
- lythossettle/web/session.py +303 -0
- lythossettle/web/static/app.js +711 -0
- lythossettle/web/static/index.html +106 -0
- lythossettle/web/static/style.css +303 -0
- lythossettle/web/strings.py +69 -0
- lythossettle-0.1.0.dist-info/METADATA +224 -0
- lythossettle-0.1.0.dist-info/RECORD +30 -0
- lythossettle-0.1.0.dist-info/WHEEL +5 -0
- lythossettle-0.1.0.dist-info/entry_points.txt +2 -0
- lythossettle-0.1.0.dist-info/licenses/LICENSE +21 -0
- lythossettle-0.1.0.dist-info/top_level.txt +1 -0
lythossettle/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lythos Settle — settlement analysis of shallow foundations, driven from a browser.
|
|
3
|
+
|
|
4
|
+
The program works out how much, and how fast, a shallow foundation settles on
|
|
5
|
+
a layered soil profile:
|
|
6
|
+
|
|
7
|
+
1. Stresses — in-situ σ'v0 and σ'p; the stress increase beneath the
|
|
8
|
+
foundation by Boussinesq (rectangle, strip, circle) or 2:1
|
|
9
|
+
2. Immediate — layered elastic (Steinbrenner) or Schmertmann (1978)
|
|
10
|
+
3. Consolidation — primary settlement of the clay layers from Cc, Cr, e0
|
|
11
|
+
and σ'p, and secondary compression from Cα
|
|
12
|
+
4. Time — Terzaghi's one-dimensional consolidation, per layer
|
|
13
|
+
5. Checks — total settlement and angular distortion
|
|
14
|
+
|
|
15
|
+
On top of it, a parametric or reliability study sweeps any input (a range, or
|
|
16
|
+
a distribution) and reports sensitivities and the probability of exceeding
|
|
17
|
+
the allowable settlement.
|
|
18
|
+
|
|
19
|
+
The interface is a local web server driven from the browser (standard library
|
|
20
|
+
only), so the program also runs over a remote session or inside a container,
|
|
21
|
+
where a desktop toolkit would need a display it does not have.
|
|
22
|
+
|
|
23
|
+
Package layout
|
|
24
|
+
--------------
|
|
25
|
+
lythossettle.config app identity, defaults, themes, palette
|
|
26
|
+
lythossettle.i18n every text of the program, English and Turkish
|
|
27
|
+
lythossettle.stress Boussinesq, 2:1 and Steinbrenner solutions
|
|
28
|
+
lythossettle.consolidation Terzaghi time factor, compression of clay
|
|
29
|
+
lythossettle.engine the settlement analysis
|
|
30
|
+
lythossettle.study parametric / reliability studies
|
|
31
|
+
lythossettle.plotting analysis figures
|
|
32
|
+
lythossettle.study_plots study figures
|
|
33
|
+
lythossettle.report calculation report: HTML, PDF, DOCX
|
|
34
|
+
lythossettle.forms input schema and readers (interface-independent)
|
|
35
|
+
lythossettle.web local web server and the browser interface
|
|
36
|
+
|
|
37
|
+
Run it: lythos-settle (or python -m lythossettle)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
__version__ = "0.1.0"
|
|
41
|
+
|
|
42
|
+
APP_NAME = "Lythos Settle"
|
|
43
|
+
ORG = "Lythos"
|
|
44
|
+
|
|
45
|
+
__all__ = ["__version__", "APP_NAME", "ORG"]
|
lythossettle/__main__.py
ADDED
lythossettle/cli.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Command line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from . import APP_NAME, __version__
|
|
10
|
+
|
|
11
|
+
#: Default port of the local interface (the family counts up from 8777)
|
|
12
|
+
PORT = 8780
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(argv=None) -> int:
|
|
16
|
+
parser = argparse.ArgumentParser(
|
|
17
|
+
prog="lythos-settle",
|
|
18
|
+
description="Settlement analysis of shallow foundations: stress distribution, "
|
|
19
|
+
"immediate (elastic / Schmertmann), consolidation and secondary "
|
|
20
|
+
"settlement, consolidation time, and parametric / reliability studies.")
|
|
21
|
+
parser.add_argument("--version", action="version", version=f"{APP_NAME} {__version__}")
|
|
22
|
+
# Running the program with no subcommand means "web", so the top level
|
|
23
|
+
# carries that subcommand's defaults: without them the bare `lythos-settle`
|
|
24
|
+
# would reach serve() with a Namespace that has no host, port or language.
|
|
25
|
+
parser.set_defaults(host="127.0.0.1", port=PORT, lang="en", no_browser=False)
|
|
26
|
+
sub = parser.add_subparsers(dest="command")
|
|
27
|
+
|
|
28
|
+
web = sub.add_parser("web", help="start the interface in a browser")
|
|
29
|
+
web.add_argument("--port", type=int, default=PORT)
|
|
30
|
+
web.add_argument("--host", default="127.0.0.1")
|
|
31
|
+
web.add_argument("--lang", default="en", choices=["en", "tr"])
|
|
32
|
+
web.add_argument("--no-browser", action="store_true")
|
|
33
|
+
|
|
34
|
+
run = sub.add_parser("run", help="analyse a project file and print the results")
|
|
35
|
+
run.add_argument("project", help="path to a .settle / .json project file")
|
|
36
|
+
run.add_argument("-o", "--out", default=None,
|
|
37
|
+
help="write a report here (.pdf / .html / .docx)")
|
|
38
|
+
run.add_argument("--lang", default="en", choices=["en", "tr"])
|
|
39
|
+
|
|
40
|
+
study = sub.add_parser("study", help="run the study defined in a project file")
|
|
41
|
+
study.add_argument("project", help="path to a .settle / .json project file")
|
|
42
|
+
study.add_argument("-o", "--out", default=None, help="write the samples here (.csv / .xlsx)")
|
|
43
|
+
study.add_argument("--lang", default="en", choices=["en", "tr"])
|
|
44
|
+
|
|
45
|
+
example = sub.add_parser("example", help="write a starter project file")
|
|
46
|
+
example.add_argument("-o", "--out", default="project.settle")
|
|
47
|
+
|
|
48
|
+
args = parser.parse_args(argv)
|
|
49
|
+
command = args.command or "web"
|
|
50
|
+
try:
|
|
51
|
+
return _dispatch(command, args)
|
|
52
|
+
except (ValueError, RuntimeError, OSError) as exc:
|
|
53
|
+
# The analysis refuses impossible input with a sentence worth reading
|
|
54
|
+
# (a foundation below the profile, a layer without a modulus, an
|
|
55
|
+
# unreadable project file). A traceback would bury it, so only
|
|
56
|
+
# unexpected failures keep theirs.
|
|
57
|
+
print(f"{APP_NAME}: {exc}", file=sys.stderr)
|
|
58
|
+
return 1
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _example_study(values: dict) -> list:
|
|
62
|
+
"""Two study variables for the starter project, so `study` has something to do."""
|
|
63
|
+
return [
|
|
64
|
+
{"path": "foundation.q", "label": "Foundation · q", "mode": "dist", "dist": "normal",
|
|
65
|
+
"mean": values["q"], "cov": 0.10, "min": 0, "max": 0, "n_points": 5},
|
|
66
|
+
{"path": "soil_profile.2.Cc", "label": "Soft clay · Cc", "mode": "dist",
|
|
67
|
+
"dist": "lognormal", "mean": values["soil_profile"][2]["Cc"], "cov": 0.25,
|
|
68
|
+
"min": 0, "max": 0, "n_points": 5},
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _dispatch(command: str, args) -> int:
|
|
73
|
+
"""Runs one command; raises on anything that goes wrong."""
|
|
74
|
+
if command == "web":
|
|
75
|
+
from .web.server import serve
|
|
76
|
+
serve(host=args.host, port=args.port, open_browser=not args.no_browser,
|
|
77
|
+
lang=args.lang)
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
from . import forms
|
|
81
|
+
|
|
82
|
+
if command == "example":
|
|
83
|
+
values = forms.defaults()
|
|
84
|
+
values["study_variables"] = _example_study(values)
|
|
85
|
+
with open(args.out, "w", encoding="utf-8") as fh:
|
|
86
|
+
json.dump(forms.project_file(values), fh, indent=2, ensure_ascii=False)
|
|
87
|
+
print(args.out)
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
with open(args.project, encoding="utf-8") as fh:
|
|
91
|
+
data = json.load(fh)
|
|
92
|
+
|
|
93
|
+
from .web.session import Session
|
|
94
|
+
session = Session(lang=args.lang)
|
|
95
|
+
values = session.load_project(data)["values"]
|
|
96
|
+
|
|
97
|
+
if command == "run":
|
|
98
|
+
result = session.analyse(values)
|
|
99
|
+
print(result["text"])
|
|
100
|
+
if args.out:
|
|
101
|
+
fmt = args.out.lower().rsplit(".", 1)[-1]
|
|
102
|
+
print(session.report(fmt if fmt in ("pdf", "html", "docx") else "pdf", args.out))
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
# A study: analyse the foundation first, so the report and the figures
|
|
106
|
+
# have something to sit beside, then sample.
|
|
107
|
+
session.analyse(values)
|
|
108
|
+
started = session.start_study(values)
|
|
109
|
+
if not started["ok"]:
|
|
110
|
+
print(started["error"], file=sys.stderr)
|
|
111
|
+
return 1
|
|
112
|
+
import time
|
|
113
|
+
last = -1
|
|
114
|
+
while session.state()["job"] == "running":
|
|
115
|
+
state = session.state()
|
|
116
|
+
if state["total"] and state["done"] != last:
|
|
117
|
+
last = state["done"]
|
|
118
|
+
print(f"\r{state['done']} / {state['total']}", end="", file=sys.stderr, flush=True)
|
|
119
|
+
time.sleep(0.2)
|
|
120
|
+
print("", file=sys.stderr)
|
|
121
|
+
state = session.state()
|
|
122
|
+
if state["job"] == "error":
|
|
123
|
+
print(state["error"], file=sys.stderr)
|
|
124
|
+
return 1
|
|
125
|
+
payload = session.study_payload()
|
|
126
|
+
if not payload["ok"]:
|
|
127
|
+
print(payload["error"], file=sys.stderr)
|
|
128
|
+
return 1
|
|
129
|
+
print(payload["text"])
|
|
130
|
+
if args.out:
|
|
131
|
+
kind = "xlsx" if args.out.lower().endswith(".xlsx") else "csv"
|
|
132
|
+
print(session.export_study(kind, args.out))
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
sys.exit(main())
|
lythossettle/config.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration for Lythos Settle: the default project, the choice lists, and
|
|
3
|
+
the theme and plot palette shared by the page and the figures.
|
|
4
|
+
|
|
5
|
+
The app name and version live in the package's ``__init__`` so that there is
|
|
6
|
+
one copy of each; the translations live in `lythossettle.i18n`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from . import APP_NAME
|
|
10
|
+
from . import __version__ as APP_VERSION
|
|
11
|
+
|
|
12
|
+
__all__ = ["APP_NAME", "APP_VERSION", "DEFAULT_CONFIG", "THEMES", "PLOT_PALETTE",
|
|
13
|
+
"SOIL_FILL", "SHAPES", "STRESS_METHODS", "IMMEDIATE_METHODS", "RIGIDITY",
|
|
14
|
+
"BEHAVIOURS", "DRAINAGE", "ACCENT"]
|
|
15
|
+
|
|
16
|
+
MM_PER_M = 1000.0
|
|
17
|
+
|
|
18
|
+
# --- Choice lists (the first entry is the default where one is needed) -----
|
|
19
|
+
SHAPES = ["rectangle", "strip", "circle", "embankment"]
|
|
20
|
+
STRESS_METHODS = ["boussinesq", "two_to_one"]
|
|
21
|
+
IMMEDIATE_METHODS = ["elastic", "schmertmann"]
|
|
22
|
+
RIGIDITY = ["flexible", "rigid"]
|
|
23
|
+
BEHAVIOURS = ["granular", "cohesive"]
|
|
24
|
+
DRAINAGE = ["double", "single"]
|
|
25
|
+
|
|
26
|
+
# --- Interface theme (web/static/style.css) and plot palette ---------------
|
|
27
|
+
# --- kept together so the figures always match the page they are shown on. -
|
|
28
|
+
ACCENT = "#C6613F"
|
|
29
|
+
|
|
30
|
+
THEMES = {
|
|
31
|
+
"dark": dict(bg="#262624", panel="#30302E", input_bg="#262624", fg="#F5F4ED",
|
|
32
|
+
fg_dim="#A6A39A", border="#4A4944", hover="#3A3A37", btn="#3A3A37",
|
|
33
|
+
muted="#6B6A64", accent="#D97757", accent_hover="#E08B6E"),
|
|
34
|
+
"light": dict(bg="#F5F4ED", panel="#FAF9F5", input_bg="#FFFFFF", fg="#141413",
|
|
35
|
+
fg_dim="#73726C", border="#E3E0D5", hover="#F0EEE6", btn="#F0EEE6",
|
|
36
|
+
muted="#B7B4AA", accent=ACCENT, accent_hover="#B0532F"),
|
|
37
|
+
}
|
|
38
|
+
# The report's figures: the light palette on white paper.
|
|
39
|
+
THEMES["paper"] = dict(THEMES["light"], bg="#FFFFFF", panel="#FFFFFF")
|
|
40
|
+
|
|
41
|
+
# Semantic colours: the same quantity is the same colour in every figure. Warm and
|
|
42
|
+
# muted, to sit on the paper-coloured page; terracotta marks the total.
|
|
43
|
+
PLOT_PALETTE = dict(
|
|
44
|
+
immediate="#5B8DB8", consolidation="#8C6BB1", secondary="#D9A55B",
|
|
45
|
+
total="#C6613F", stress="#4E9A8A", overburden="#73726C", preconsolidation="#5E8C4A",
|
|
46
|
+
limit="#A26A12", water="#6FA8C7", footing="#8A8680",
|
|
47
|
+
center="#C6613F", char="#8C6BB1", edge="#D9A55B", corner="#4E9A8A",
|
|
48
|
+
shoulder="#8C6BB1", midslope="#D9A55B", toe="#4E9A8A", fill="#C9A876",
|
|
49
|
+
allowable="#B0413E",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Fill colours of the two soil behaviours in the schematic (theme-dependent,
|
|
53
|
+
# since a light sandy tone reads poorly on a dark background).
|
|
54
|
+
SOIL_FILL = {
|
|
55
|
+
"light": {"granular": "#E3C98F", "cohesive": "#A7B8A0"},
|
|
56
|
+
"dark": {"granular": "#8A7250", "cohesive": "#5E6E58"},
|
|
57
|
+
}
|
|
58
|
+
SOIL_FILL["paper"] = SOIL_FILL["light"]
|
|
59
|
+
|
|
60
|
+
DEFAULT_CONFIG = {
|
|
61
|
+
"project_info": {
|
|
62
|
+
"title": "Project: Raft on soft clay",
|
|
63
|
+
"analyst": "",
|
|
64
|
+
},
|
|
65
|
+
"foundation": {
|
|
66
|
+
"shape": "rectangle",
|
|
67
|
+
"B": 8.0, # width, or diameter of a circle [m]
|
|
68
|
+
"L": 16.0, # length of a rectangle [m]
|
|
69
|
+
"Df": 1.5, # depth of the foundation base [m]
|
|
70
|
+
"q": 100.0, # applied (gross) bearing pressure [kPa]
|
|
71
|
+
"net_pressure": True, # deduct the overburden removed by the excavation
|
|
72
|
+
},
|
|
73
|
+
# An embankment (shape "embankment"): a long trapezoidal fill on the ground
|
|
74
|
+
# surface, loading it with γ·H under the crest and less under the slopes.
|
|
75
|
+
"embankment": {
|
|
76
|
+
"crest": 12.0, # crest width [m]
|
|
77
|
+
"height": 4.0, # [m]
|
|
78
|
+
"slope_left": 26.57, # slope angles from the horizontal [°] (1V:2H)
|
|
79
|
+
"slope_right": 26.57,
|
|
80
|
+
"gamma": 20.0, # unit weight of the fill [kN/m³]
|
|
81
|
+
},
|
|
82
|
+
"groundwater": {
|
|
83
|
+
"depth": 2.0, # below ground surface [m]
|
|
84
|
+
"gamma_water": 9.81, # [kN/m³]
|
|
85
|
+
},
|
|
86
|
+
# Layers from the ground surface down. E [MPa] is the drained modulus of
|
|
87
|
+
# a granular layer and the undrained modulus of a cohesive one; cv in
|
|
88
|
+
# m²/year; OCR = σ'p / σ'v0.
|
|
89
|
+
"soil_profile": [
|
|
90
|
+
{"name": "Fill", "thickness": 1.5, "behaviour": "granular", "gamma": 18.0,
|
|
91
|
+
"gamma_sat": 19.0, "E": 10.0, "nu": 0.30, "Cc": 0.0, "Cr": 0.0, "e0": 0.6,
|
|
92
|
+
"OCR": 1.0, "cv": 0.0, "Calpha": 0.0, "drainage": "double"},
|
|
93
|
+
{"name": "Medium dense sand", "thickness": 3.5, "behaviour": "granular",
|
|
94
|
+
"gamma": 18.5, "gamma_sat": 20.0, "E": 25.0, "nu": 0.30, "Cc": 0.0, "Cr": 0.0,
|
|
95
|
+
"e0": 0.6, "OCR": 1.0, "cv": 0.0, "Calpha": 0.0, "drainage": "double"},
|
|
96
|
+
{"name": "Soft clay", "thickness": 6.0, "behaviour": "cohesive", "gamma": 17.0,
|
|
97
|
+
"gamma_sat": 17.5, "E": 6.0, "nu": 0.50, "Cc": 0.32, "Cr": 0.05, "e0": 1.05,
|
|
98
|
+
"OCR": 1.3, "cv": 1.5, "Calpha": 0.010, "drainage": "double"},
|
|
99
|
+
{"name": "Dense sand", "thickness": 10.0, "behaviour": "granular", "gamma": 19.5,
|
|
100
|
+
"gamma_sat": 21.0, "E": 60.0, "nu": 0.30, "Cc": 0.0, "Cr": 0.0, "e0": 0.5,
|
|
101
|
+
"OCR": 1.0, "cv": 0.0, "Calpha": 0.0, "drainage": "double"},
|
|
102
|
+
],
|
|
103
|
+
"options": {
|
|
104
|
+
"stress_method": "boussinesq",
|
|
105
|
+
"immediate_method": "elastic",
|
|
106
|
+
"rigidity": "flexible",
|
|
107
|
+
"sublayer": 0.25, # thickness of the calculation sublayers [m]
|
|
108
|
+
"depth_ratio": 0.10, # influence depth where Δσ = ratio · σ'v0 (0: whole profile)
|
|
109
|
+
"design_life": 50.0, # years, for secondary compression and creep
|
|
110
|
+
"creep": True, # Schmertmann's time factor C2
|
|
111
|
+
},
|
|
112
|
+
"criteria": {
|
|
113
|
+
"s_allow": 150.0, # allowable total settlement [mm] (0: no check)
|
|
114
|
+
"distortion_allow": 500.0, # allowable angular distortion 1 / x (0: no check)
|
|
115
|
+
},
|
|
116
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
One-dimensional consolidation: Terzaghi's theory, and the settlement of a
|
|
3
|
+
clay sublayer from its compression indices.
|
|
4
|
+
|
|
5
|
+
Time
|
|
6
|
+
U(Tv) is the average degree of consolidation for a uniform initial excess
|
|
7
|
+
pore pressure: the Fourier series, with Terzaghi's √(4Tv/π) where the
|
|
8
|
+
series would need too many terms. Tv(U) is its inverse, found by
|
|
9
|
+
bisection so that U(Tv(U)) = U to the precision of the series, rather
|
|
10
|
+
than by the usual two-branch approximation.
|
|
11
|
+
|
|
12
|
+
Magnitude
|
|
13
|
+
Δe from Cc and Cr about the preconsolidation pressure σ'p: the
|
|
14
|
+
recompression line up to σ'p and the virgin line beyond it.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
#: Terms of the Fourier series (plenty for Tv ≥ 1e-3)
|
|
22
|
+
TERMS = 200
|
|
23
|
+
|
|
24
|
+
#: Below this Tv the series is replaced by √(4Tv/π) (identical there to 1e-9)
|
|
25
|
+
TV_SMALL = 1e-3
|
|
26
|
+
|
|
27
|
+
#: Degree of consolidation taken as the end of primary consolidation, from
|
|
28
|
+
#: which secondary compression is counted
|
|
29
|
+
U_END_OF_PRIMARY = 0.95
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def degree(Tv) -> np.ndarray:
|
|
33
|
+
"""Average degree of consolidation U for time factor(s) Tv."""
|
|
34
|
+
Tv = np.atleast_1d(np.asarray(Tv, dtype=float))
|
|
35
|
+
Tv = np.maximum(Tv, 0.0)
|
|
36
|
+
m = np.arange(TERMS)
|
|
37
|
+
M = np.pi * (2 * m + 1) / 2.0
|
|
38
|
+
series = 1.0 - np.sum(2.0 / M ** 2 * np.exp(-np.outer(np.maximum(Tv, TV_SMALL), M ** 2)),
|
|
39
|
+
axis=1)
|
|
40
|
+
small = np.sqrt(4.0 * Tv / np.pi)
|
|
41
|
+
return np.clip(np.where(Tv < TV_SMALL, small, series), 0.0, 1.0)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def time_factor(U: float) -> float:
|
|
45
|
+
"""Time factor Tv at which the average degree of consolidation is U."""
|
|
46
|
+
if not 0.0 < U < 1.0:
|
|
47
|
+
raise ValueError("the degree of consolidation must be between 0 and 1")
|
|
48
|
+
lo, hi = 0.0, 10.0
|
|
49
|
+
for _ in range(80):
|
|
50
|
+
mid = 0.5 * (lo + hi)
|
|
51
|
+
if degree(mid)[0] < U:
|
|
52
|
+
lo = mid
|
|
53
|
+
else:
|
|
54
|
+
hi = mid
|
|
55
|
+
return 0.5 * (lo + hi)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def drainage_path(thickness: float, drainage: str) -> float:
|
|
59
|
+
"""Longest drainage path: half the layer when both faces drain."""
|
|
60
|
+
return thickness / 2.0 if drainage == "double" else thickness
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def time_for(U: float, cv: float, h_dr: float) -> float:
|
|
64
|
+
"""Time (in the units of cv) to reach degree of consolidation U."""
|
|
65
|
+
if cv <= 0:
|
|
66
|
+
return float("nan")
|
|
67
|
+
return time_factor(U) * h_dr * h_dr / cv
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def degree_at(t, cv: float, h_dr: float) -> np.ndarray:
|
|
71
|
+
"""U at time(s) t for a layer with coefficient cv and drainage path h_dr.
|
|
72
|
+
|
|
73
|
+
A layer without cv is taken as consolidating at once (U = 1)."""
|
|
74
|
+
t = np.atleast_1d(np.asarray(t, dtype=float))
|
|
75
|
+
if cv <= 0 or h_dr <= 0:
|
|
76
|
+
return np.ones_like(t)
|
|
77
|
+
return degree(cv * t / (h_dr * h_dr))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def primary_strain(sigma0, dsigma, sigma_p, Cc: float, Cr: float, e0: float) -> np.ndarray:
|
|
81
|
+
"""Vertical strain Δe / (1 + e0) of clay loaded from σ'0 by Δσ.
|
|
82
|
+
|
|
83
|
+
σ'0 ≥ σ'p is normally consolidated; below it the recompression line is
|
|
84
|
+
followed up to σ'p (or the final stress, whichever is lower) and the
|
|
85
|
+
virgin line beyond.
|
|
86
|
+
"""
|
|
87
|
+
s0 = np.maximum(np.atleast_1d(np.asarray(sigma0, dtype=float)), 1e-6)
|
|
88
|
+
sf = np.maximum(s0 + np.maximum(np.asarray(dsigma, dtype=float), 0.0), s0)
|
|
89
|
+
sp = np.maximum(np.asarray(sigma_p, dtype=float), s0)
|
|
90
|
+
recompression = Cr * np.log10(np.minimum(sf, sp) / s0)
|
|
91
|
+
virgin = Cc * np.log10(np.maximum(sf, sp) / sp)
|
|
92
|
+
return (recompression + virgin) / (1.0 + e0)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def secondary_strain(t, t_p: float, Calpha: float, e0: float) -> np.ndarray:
|
|
96
|
+
"""Secondary compression strain Cα/(1 + e0)·log10(t / t_p), zero before t_p."""
|
|
97
|
+
t = np.atleast_1d(np.asarray(t, dtype=float))
|
|
98
|
+
if Calpha <= 0 or not np.isfinite(t_p) or t_p <= 0:
|
|
99
|
+
return np.zeros_like(t)
|
|
100
|
+
return Calpha / (1.0 + e0) * np.log10(np.maximum(t / t_p, 1.0))
|