Sompote commited on
Commit
18e2c35
·
verified ·
1 Parent(s): 5fe00fa

Upload app.py

Browse files
Files changed (1) hide show
  1. 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)