Spaces:
Sleeping
Sleeping
Upload app.py
Browse files- src/app.py +160 -0
src/app.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import numpy as np
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import altair as alt
|
| 5 |
+
|
| 6 |
+
def calculate_pile_loads(P, Mx, My, pile_coords):
|
| 7 |
+
"""
|
| 8 |
+
Calculates the load on each pile in a group subjected to a vertical load and moments.
|
| 9 |
+
|
| 10 |
+
Args:
|
| 11 |
+
P (float): The total vertical load applied to the pile cap (in kN).
|
| 12 |
+
Mx (float): The moment about the x-axis (in kN-m).
|
| 13 |
+
My (float): The moment about the y-axis (in kN-m).
|
| 14 |
+
pile_coords (list of tuples): A list of (x, y) coordinates for each pile.
|
| 15 |
+
|
| 16 |
+
Returns:
|
| 17 |
+
list: A list of the calculated loads on each pile.
|
| 18 |
+
"""
|
| 19 |
+
n = len(pile_coords)
|
| 20 |
+
if n == 0:
|
| 21 |
+
return []
|
| 22 |
+
|
| 23 |
+
x_coords = np.array([coord[0] for coord in pile_coords])
|
| 24 |
+
y_coords = np.array([coord[1] for coord in pile_coords])
|
| 25 |
+
|
| 26 |
+
sum_x_sq = np.sum(x_coords**2)
|
| 27 |
+
sum_y_sq = np.sum(y_coords**2)
|
| 28 |
+
|
| 29 |
+
loads = []
|
| 30 |
+
for i in range(n):
|
| 31 |
+
x_i = x_coords[i]
|
| 32 |
+
y_i = y_coords[i]
|
| 33 |
+
|
| 34 |
+
load_P = P / n
|
| 35 |
+
load_Mx = (Mx * y_i) / sum_y_sq if sum_y_sq != 0 else 0
|
| 36 |
+
load_My = (My * x_i) / sum_x_sq if sum_x_sq != 0 else 0
|
| 37 |
+
|
| 38 |
+
total_load = load_P + load_Mx + load_My
|
| 39 |
+
loads.append(total_load)
|
| 40 |
+
|
| 41 |
+
return loads
|
| 42 |
+
|
| 43 |
+
st.set_page_config(layout="wide", page_title="Pile Load Calculator")
|
| 44 |
+
|
| 45 |
+
st.title("Pile Load Calculator")
|
| 46 |
+
st.write("This application calculates the load on each pile in a foundation based on applied loads and moments.")
|
| 47 |
+
|
| 48 |
+
# --- Sidebar for Inputs ---
|
| 49 |
+
st.sidebar.header("Input Parameters")
|
| 50 |
+
|
| 51 |
+
# Applied Loads
|
| 52 |
+
st.sidebar.subheader("Applied Loads (kN, kN-m)")
|
| 53 |
+
P = st.sidebar.number_input("Vertical Point Load (P)", value=4500.0, step=100.0)
|
| 54 |
+
Mx = st.sidebar.number_input("Moment about X-axis (Mx)", value=680.0, step=50.0)
|
| 55 |
+
My = st.sidebar.number_input("Moment about Y-axis (My)", value=400.0, step=50.0)
|
| 56 |
+
|
| 57 |
+
# Footing Self-Weight
|
| 58 |
+
st.sidebar.subheader("Footing Self-Weight (kN)")
|
| 59 |
+
footing_self_weight = st.sidebar.number_input("Footing Self-Weight", value=225.0, step=25.0)
|
| 60 |
+
|
| 61 |
+
# Pile Coordinates
|
| 62 |
+
st.sidebar.subheader("Pile Coordinates (meters)")
|
| 63 |
+
if 'pile_coords' not in st.session_state:
|
| 64 |
+
st.session_state.pile_coords = [(-3, 3), (0, 3), (3, 3),(-3, 0), (0, 0), (3, 0),(-3, -3),(0, -3),(3, -3)]
|
| 65 |
+
|
| 66 |
+
def add_pile():
|
| 67 |
+
st.session_state.pile_coords.append((0.0, 0.0))
|
| 68 |
+
|
| 69 |
+
def remove_pile(index):
|
| 70 |
+
st.session_state.pile_coords.pop(index)
|
| 71 |
+
|
| 72 |
+
for i, (x, y) in enumerate(st.session_state.pile_coords):
|
| 73 |
+
cols = st.sidebar.columns([2, 2, 1])
|
| 74 |
+
new_x = cols[0].number_input(f"Pile {i+1} X", value=float(x), key=f"x{i}")
|
| 75 |
+
new_y = cols[1].number_input(f"Pile {i+1} Y", value=float(y), key=f"y{i}")
|
| 76 |
+
st.session_state.pile_coords[i] = (new_x, new_y)
|
| 77 |
+
if cols[2].button("X", key=f"del{i}"):
|
| 78 |
+
remove_pile(i)
|
| 79 |
+
st.rerun()
|
| 80 |
+
|
| 81 |
+
st.sidebar.button("Add Pile", on_click=add_pile)
|
| 82 |
+
|
| 83 |
+
# --- Main Panel for Results ---
|
| 84 |
+
total_vertical_load = P + footing_self_weight
|
| 85 |
+
pile_loads = calculate_pile_loads(total_vertical_load, Mx, My, st.session_state.pile_coords)
|
| 86 |
+
|
| 87 |
+
# Display Results in a Table
|
| 88 |
+
st.header("Calculation Results")
|
| 89 |
+
|
| 90 |
+
results_df = pd.DataFrame({
|
| 91 |
+
'Pile': [f"Pile {i+1}" for i in range(len(st.session_state.pile_coords))],
|
| 92 |
+
'X-coordinate (m)': [f"{c[0]:.2f}" for c in st.session_state.pile_coords],
|
| 93 |
+
'Y-coordinate (m)': [f"{c[1]:.2f}" for c in st.session_state.pile_coords],
|
| 94 |
+
'Calculated Load (kN)': [f"{l:.2f}" for l in pile_loads]
|
| 95 |
+
})
|
| 96 |
+
|
| 97 |
+
st.dataframe(results_df.set_index('Pile'))
|
| 98 |
+
|
| 99 |
+
# Display Summary of Loads
|
| 100 |
+
st.subheader("Load Summary")
|
| 101 |
+
st.write(f"**Total Applied Vertical Load (P + Self-Weight):** {total_vertical_load:.2f} kN")
|
| 102 |
+
st.write(f"**Maximum Pile Load (Compression):** {max(pile_loads):.2f} kN")
|
| 103 |
+
st.write(f"**Minimum Pile Load (Tension/Uplift):** {min(pile_loads):.2f} kN")
|
| 104 |
+
|
| 105 |
+
# Visualization of Pile Loads
|
| 106 |
+
st.header("Pile Load Visualization")
|
| 107 |
+
|
| 108 |
+
if st.session_state.pile_coords:
|
| 109 |
+
vis_df = pd.DataFrame({
|
| 110 |
+
'x': [c[0] for c in st.session_state.pile_coords],
|
| 111 |
+
'y': [c[1] for c in st.session_state.pile_coords],
|
| 112 |
+
'load': pile_loads,
|
| 113 |
+
'load_text': [f"{l:.1f} kN" for l in pile_loads]
|
| 114 |
+
})
|
| 115 |
+
|
| 116 |
+
# Determine the domain for the color scale to center on zero
|
| 117 |
+
max_abs_load = max(abs(vis_df['load'].min()), abs(vis_df['load'].max()))
|
| 118 |
+
|
| 119 |
+
# Calculate padding for the chart domain
|
| 120 |
+
x_range = vis_df['x'].max() - vis_df['x'].min()
|
| 121 |
+
y_range = vis_df['y'].max() - vis_df['y'].min()
|
| 122 |
+
x_buffer = x_range * 0.2 # 20% buffer
|
| 123 |
+
y_buffer = y_range * 0.2 # 20% buffer
|
| 124 |
+
|
| 125 |
+
base_chart = alt.Chart(vis_df).encode(
|
| 126 |
+
x=alt.X('x:Q', title='X-coordinate (m)',
|
| 127 |
+
scale=alt.Scale(domain=[vis_df['x'].min() - x_buffer, vis_df['x'].max() + x_buffer]),
|
| 128 |
+
axis=alt.Axis(titleFontSize=14, labelFontSize=12)),
|
| 129 |
+
y=alt.Y('y:Q', title='Y-coordinate (m)',
|
| 130 |
+
scale=alt.Scale(domain=[vis_df['y'].min() - y_buffer, vis_df['y'].max() + y_buffer]),
|
| 131 |
+
axis=alt.Axis(titleFontSize=14, labelFontSize=12)),
|
| 132 |
+
tooltip=[
|
| 133 |
+
alt.Tooltip('x:Q', title='X-coordinate', format='.2f'),
|
| 134 |
+
alt.Tooltip('y:Q', title='Y-coordinate', format='.2f'),
|
| 135 |
+
alt.Tooltip('load:Q', title='Load (kN)', format='.2f')
|
| 136 |
+
]
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# Points with color scale
|
| 140 |
+
points = base_chart.mark_point(size=300, filled=True, stroke='black', strokeWidth=0.5).encode(
|
| 141 |
+
color=alt.Color('load:Q', title='Load (kN)',
|
| 142 |
+
scale=alt.Scale(scheme='redblue', domain=[-max_abs_load, max_abs_load], reverse=True))
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
# Text labels for the loads
|
| 146 |
+
text = base_chart.mark_text(align='center', dy=-15, fontSize=12).encode(
|
| 147 |
+
text='load_text:N'
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
chart = (points + text).properties(
|
| 151 |
+
title=alt.TitleParams(
|
| 152 |
+
text='Pile Location and Load Distribution',
|
| 153 |
+
subtitle='Red for Tension/Uplift, Blue for Compression',
|
| 154 |
+
fontSize=20,
|
| 155 |
+
subtitleFontSize=16
|
| 156 |
+
),
|
| 157 |
+
padding={"left": 20, "top": 20, "right": 20, "bottom": 20} # Add padding
|
| 158 |
+
).interactive()
|
| 159 |
+
|
| 160 |
+
st.altair_chart(chart, use_container_width=True)
|