commit a28cde3903ab3f62eecfe80b157422c2ea39793f Author: Alexander Rossmanith Date: Fri Aug 7 20:11:16 2026 +0530 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dd47c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.py[codz] +__pycache__/ +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ +.pdm-python diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..85cdb16 --- /dev/null +++ b/pdm.lock @@ -0,0 +1,90 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:758d4d2ec35c05f925622890ce94547018729181f38472c8cd765765109d4eec" + +[[metadata.targets]] +requires_python = "==3.14.*" + +[[package]] +name = "numpy" +version = "2.4.4" +requires_python = ">=3.11" +summary = "Fundamental package for array computing in Python" +groups = ["default"] +files = [ + {file = "numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b"}, + {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a"}, + {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d"}, + {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252"}, + {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f"}, + {file = "numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc"}, + {file = "numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74"}, + {file = "numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d"}, + {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d"}, + {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f"}, + {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0"}, + {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150"}, + {file = "numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871"}, + {file = "numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e"}, + {file = "numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7"}, + {file = "numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0"}, +] + +[[package]] +name = "pypassive" +version = "0.0.1" +requires_python = ">=3.11" +summary = "A simple python package to compute p-y curve using log-spiral method and hyperbolic relationship." +groups = ["default"] +dependencies = [ + "numpy>=1.26.2", + "scipy>=1.11.4", +] +files = [ + {file = "pypassive-0.0.1-py3-none-any.whl", hash = "sha256:7c71a675459878117c7d02f232565e4ef549be9aafd4f01b707394a98c80f840"}, + {file = "pypassive-0.0.1.tar.gz", hash = "sha256:40519cc33801cd37896c75f7a86ff3e50ecd765c077841a521d640539ee339a2"}, +] + +[[package]] +name = "scipy" +version = "1.17.1" +requires_python = ">=3.11" +summary = "Fundamental algorithms for scientific computing in Python" +groups = ["default"] +dependencies = [ + "numpy<2.7,>=1.26.4", +] +files = [ + {file = "scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717"}, + {file = "scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9"}, + {file = "scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b"}, + {file = "scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866"}, + {file = "scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350"}, + {file = "scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118"}, + {file = "scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068"}, + {file = "scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118"}, + {file = "scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19"}, + {file = "scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293"}, + {file = "scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6"}, + {file = "scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1"}, + {file = "scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39"}, + {file = "scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca"}, + {file = "scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad"}, + {file = "scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a"}, + {file = "scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4"}, + {file = "scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2"}, + {file = "scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484"}, + {file = "scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21"}, + {file = "scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..554f6d4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "retain" +version = "0.1.0" +description = "Default template for PDM package" +authors = [ + {name = "Alexander Rossmanith", email = ""}, +] +dependencies = ["pypassive>=0.0.1"] +requires-python = "==3.14.*" +readme = "README.md" +license = {text = "MIT"} + + +[tool.pdm] +distribution = false diff --git a/retain/__init__.py b/retain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/retain/main.py b/retain/main.py new file mode 100644 index 0000000..ac7687e --- /dev/null +++ b/retain/main.py @@ -0,0 +1,269 @@ +import math + +def main(): + # --- Soil parameters --- + # phi = angle of internal friction: The angle that describes the shear strength of soil due to friction between particles. Unit: radians. + # gamma = unit weight: The weight of soil per unit volume. Unit: kilonewtons per cubic meter (kN/m³). + # c = cohesion: The shear strength of soil that comes from the bonding between particles, independent of normal stress. Unit: kilopascals (kPa). + # q_allow = allowable soil bearing capacity: The maximum pressure that the soil can safely support without risk of shear failure or excessive settlement. Unit: kN/m². + # q_allow should be confirmed with plate load test or SPT at foundation depth + # + # Sand: phi = 30-40°, gamma = 18-20 kN/m³ + # Clay: phi = 15-25°, gamma = 17-19 kN/m³, c=10-50 kPa + # Silt: phi = 25-35°, gamma = 17-19 kN/m³ + # Gravel: phi = 35-45°, gamma = 19-22 kN/m³ + + # Laterite/forest hill soils: Typically clayey with gravel inclusions, so φ ≈ 20°–28° is a reasonable assumption. + # If your site has compacted laterite with gravel, φ can be closer to 28°–32°. + + phi = math.radians(30.0) + q_allow = 150.0 # kN/m² Allowable soil bearing capacity (typical for compacted laterite with gravel), + gamma = 18.0 # Soil unit weight (kN/m³) + c = 15.0 # Cohesion (kPa) + + # --- Seismic parameters --- + kh = 0.05 # Seismic coefficient (Zone III) + + # --- Wall parameters --- + Hw = 3.3 # Wall height (m) + Hf = 0.5 # Foundation depth (m) + H = Hw + Hf # Total height for earth pressure calculations + surcharge = 10.0 # Surcharge load (kN/m²) + delta = phi / 2 # Wall friction angle (radians) + beta = math.radians(0.0) # Backfill slope angle (radians) + base_width = 2.1 # m + batter = 1/3.5 # Wall batter (horizontal/vertical) + top_width = base_width - batter * H + print(f"Base width: {base_width:.2f} m, Top width: {top_width:.2f} m") + print("Height (H): {:.2f} m, Soil unit weight (γ): {:.2f} kN/m³, φ: {:.1f}°, Surcharge: {:.2f} kN/m²".format(H, gamma, math.degrees(phi), surcharge)) + + # --- Wall material properties --- + # "black stones" = basalt or dolerite rubble quarried locally. + wall_density = 30.0 # kN/m³ + + # --- EARTH PRESSURE COEFFICIENTS --- + Ka = rankine_Ka(phi) + Kp = rankine_Kp(phi) + # Ka = coulomb_Ka(phi, delta, beta) + # Kp = coulomb_Kp(phi, delta, beta) + + # --- EARTH PRESSURES --- + Pa = 0.5 * gamma * H**2 * Ka + surcharge * H * Ka # Active pressure + Pp = 0.5 * gamma * H**2 * Kp # Passive resistance + + print(f"Active pressure (Pa): {Pa:.2f} kN/m") + print(f"Passive resistance (Pp): {Pp:.2f} kN/m") + + # --- STABILITY CHECKS --- + wall_weight = wall_density * trapezoidal_prism_volume(H, base_width, top_width) + FS_sliding = Pp / Pa + resisting_moment = wall_weight * (base_width/2) + driving_moment = Pa * (Hw/3) + FS_overturning = resisting_moment / driving_moment + q = wall_weight / base_width + + print(f"Sliding FS: {FS_sliding:.2f} (required: 1.5)") + print(f"Overturning FS: {FS_overturning:.2f} (required: 2.0)") + print(f"Base pressure: {q:.2f} kN/m²") + + # --- Bearing Check --- + M_net = resisting_moment - driving_moment + q_max, q_min, e, safe, suggestion = bearing_check(wall_weight, base_width, M_net, q_allow) + + print(f"Eccentricity: {e:.3f} m") + print(f"Toe pressure (q_max): {q_max:.2f} kN/m²") + print(f"Heel pressure (q_min): {q_min:.2f} kN/m²") + print(f"Bearing check safe? {safe}") + if suggestion: + print("Suggestion:", suggestion) + + # --- Bearing Check (Terzaghi + Meyerhof) --- + M_net = resisting_moment - driving_moment + # Location of resultant from toe + x_res = M_net / wall_weight + # Eccentricity = distance from centroid (B/2) + e = x_res - base_width / 2 + q_ult, q_applied, FS_bearing, B_eff = bearing_capacity_terzaghi(phi, c, gamma, base_width, wall_weight, e) + + middle_third_limit = base_width / 6 + print(f"Eccentricity: {e:.3f} m") + print(f"Middle third limit: {middle_third_limit:.3f} m") + if abs(e) <= middle_third_limit: + print("✓ Resultant lies within middle third (no heel tension)") + else: + print("✗ Outside middle third (heel tension likely)") + + + print(f"Effective base width: {B_eff:.2f} m") + print(f"Applied pressure: {q_applied:.2f} kN/m²") + print(f"Ultimate capacity: {q_ult:.2f} kN/m²") + print(f"Bearing FS: {FS_bearing:.2f} (required: 2.5)") + + # --- Volume and Material Estimation --- + volume = trapezoidal_prism_volume(H, base_width, top_width) + void_ratio = 0.1 # Assumed void ratio for rubble fill + material_volume = volume * (1 + void_ratio) + material_weight = material_volume * wall_density + print(f"Estimated material volume per meter of wall (including voids): {cubic_meters_to_cubic_feet(material_volume):.2f} ft³") + print(f"Estimated material weight per meter of wallin kg: {material_weight*1000/9.81:.0f} kg") + +def rankine_Ka(phi): + # Rankine active earth pressure coefficient for vertical wall, horizontal backfill + return math.tan(math.pi/4 - phi/2)**2 + +def rankine_Kp(phi): + return math.tan(math.pi/4 + phi/2)**2 + +def coulomb_Ka(phi, delta, beta): + ''' + Coulomb active earth pressure coefficient + + Args: + phi: Soil friction angle (radians) + delta: Wall friction angle (radians) + beta: Backfill slope angle (radians) + + Returns: + Ka: Active earth pressure coefficient + ''' + num = math.cos(phi - beta)**2 + den = math.cos(beta) * math.cos(delta + phi) + val = (math.sin(phi + delta) * math.sin(phi - beta)) / (math.cos(delta + phi) * math.cos(beta)) + if val < 0: + raise ValueError("Invalid geometry") + return num / (den * (1 + math.sqrt(val))**2) + +def coulomb_Kp(phi, delta, beta=0): + ''' + Coulomb passive earth pressure coefficient + + Args: + phi: Soil friction angle (radians) + delta: Wall friction angle (radians) + beta: Backfill slope angle (radians, default 0 for horizontal backfill) + + Returns: + Kp: Passive earth pressure coefficient + ''' + num = math.cos(phi + beta)**2 + den = math.cos(beta) * math.cos(beta - delta) + val = (math.sin(phi + delta) * math.sin(phi - beta)) / (math.cos(beta - delta) * math.cos(beta + phi)) + if val < 0: + raise ValueError("Invalid geometry") + return num / (den * (1 - math.sqrt(val))**2) + +def mononobe_okabe_Ka(phi, delta, beta, kh, kv=0): + ''' + Mononobe-Okabe seismic active earth pressure coefficient + + Args: + phi: Soil friction angle (radians) + delta: Wall friction angle (radians) + beta: Backfill slope angle (radians) + kh: Horizontal seismic coefficient + kv: Vertical seismic coefficient (default 0) + + Returns: + Ka: Seismic active earth pressure coefficient + ''' + theta = math.atan(kh / (1 - kv)) + num = math.cos(phi - theta)**2 + den = math.cos(theta) * math.cos(delta + theta) + val = (math.sin(phi + delta) * math.sin(phi - beta - theta)) / (math.cos(delta + theta) * math.cos(beta + phi)) + if val < 0: + raise ValueError("Invalid geometry") + return num / (den * (1 + math.sqrt(val))**2) + + +def bearing_capacity_terzaghi(phi, c, gamma_soil, B, W, e): + phi_rad = phi + Nq = math.exp(math.pi * math.tan(phi_rad)) * math.tan(math.pi/4 + phi_rad/2)**2 + Nc = (Nq - 1) / math.tan(phi_rad) + Nγ = 2 * (Nq + 1) * math.tan(phi_rad) + + # Effective width (Meyerhof correction) + B_eff = max(0.1, B - 2*abs(e)) + + # Shape factors (simplified) + sc = 1 + 0.2 * (B_eff / B) + sγ = 1 - 0.3 * (B_eff / B) + + # Ultimate capacity + q_ult = c * Nc * sc + 0.5 * gamma_soil * B_eff * Nγ * sγ + q_applied = W / B_eff + + FS_bearing = q_ult / q_applied + return q_ult, q_applied, FS_bearing, B_eff + + +def bearing_check(W, B, M, q_allow): + ''' + Calculate bearing pressures and check against allowable soil capacity. + Also suggest base width adjustment if heel pressure is negative. + + Args: + W: Total vertical load (kN) + B: Base width (m) + M: Net moment about toe (kN-m) + q_allow: Allowable soil bearing capacity (kN/m²) + + Returns: + q_max: Pressure at toe (kN/m²) + q_min: Pressure at heel (kN/m²) + e: Eccentricity (m) + safe: Boolean indicating if bearing check is safe + suggestion: Text suggestion if unsafe + ''' + e = M / W # eccentricity + q_avg = W / B + q_max = q_avg * (1 + 6*e/B) + q_min = q_avg * (1 - 6*e/B) + + safe = True + suggestion = None + + if q_min < 0: + safe = False + # Minimum base width to eliminate heel tension: B_min = 6e + B_min = 6 * e + suggestion = f"Increase base width to at least {B_min:.2f} m to avoid heel tension." + + if q_max > q_allow: + safe = False + suggestion = f"Reduce pressure or increase base width; q_max exceeds allowable {q_allow:.2f} kN/m²." + + return q_max, q_min, e, safe, suggestion + + + + +def trapezoidal_prism_volume(height, base_width, top_width, length=1.0): + """ + Calculate volume of a trapezoidal prism. + + Args: + base_width: Average base thickness + height: Total wall height including foundation + top_width: Average top thickness + length: Wall length, default = 1 (per running metre) + + Returns: + volume: Volume of the trapezoidal prism + """ + return (base_width + top_width) / 2 * height * length + + +def cubic_meters_to_cubic_feet(cubic_meters): + ''' + Convert cubic meters to cubic feet. + + Args: + cubic_meters: Volume in cubic meters + Returns: + Volume in cubic feet + ''' + return cubic_meters * 35.3147 + + +if __name__ == "__main__": + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..e69de29