File size: 5,975 Bytes
18e2c35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import streamlit as st
import numpy as np
import pandas as pd
import altair as alt

def calculate_pile_loads(P, Mx, My, pile_coords):
    """
    Calculates the load on each pile in a group subjected to a vertical load and moments.

    Args:
        P (float): The total vertical load applied to the pile cap (in kN).
        Mx (float): The moment about the x-axis (in kN-m).
        My (float): The moment about the y-axis (in kN-m).
        pile_coords (list of tuples): A list of (x, y) coordinates for each pile.

    Returns:
        list: A list of the calculated loads on each pile.
    """
    n = len(pile_coords)
    if n == 0:
        return []

    x_coords = np.array([coord[0] for coord in pile_coords])
    y_coords = np.array([coord[1] for coord in pile_coords])

    sum_x_sq = np.sum(x_coords**2)
    sum_y_sq = np.sum(y_coords**2)

    loads = []
    for i in range(n):
        x_i = x_coords[i]
        y_i = y_coords[i]

        load_P = P / n
        load_Mx = (Mx * y_i) / sum_y_sq if sum_y_sq != 0 else 0
        load_My = (My * x_i) / sum_x_sq if sum_x_sq != 0 else 0

        total_load = load_P + load_Mx + load_My
        loads.append(total_load)

    return loads

st.set_page_config(layout="wide", page_title="Pile Load Calculator")

st.title("Pile Load Calculator")
st.write("This application calculates the load on each pile in a foundation based on applied loads and moments.")

# --- Sidebar for Inputs ---
st.sidebar.header("Input Parameters")

# Applied Loads
st.sidebar.subheader("Applied Loads (kN, kN-m)")
P = st.sidebar.number_input("Vertical Point Load (P)", value=4500.0, step=100.0)
Mx = st.sidebar.number_input("Moment about X-axis (Mx)", value=680.0, step=50.0)
My = st.sidebar.number_input("Moment about Y-axis (My)", value=400.0, step=50.0)

# Footing Self-Weight
st.sidebar.subheader("Footing Self-Weight (kN)")
footing_self_weight = st.sidebar.number_input("Footing Self-Weight", value=225.0, step=25.0)

# Pile Coordinates
st.sidebar.subheader("Pile Coordinates (meters)")
if 'pile_coords' not in st.session_state:
    st.session_state.pile_coords = [(-3, 3), (0, 3), (3, 3),(-3, 0), (0, 0), (3, 0),(-3, -3),(0, -3),(3, -3)]

def add_pile():
    st.session_state.pile_coords.append((0.0, 0.0))

def remove_pile(index):
    st.session_state.pile_coords.pop(index)

for i, (x, y) in enumerate(st.session_state.pile_coords):
    cols = st.sidebar.columns([2, 2, 1])
    new_x = cols[0].number_input(f"Pile {i+1} X", value=float(x), key=f"x{i}")
    new_y = cols[1].number_input(f"Pile {i+1} Y", value=float(y), key=f"y{i}")
    st.session_state.pile_coords[i] = (new_x, new_y)
    if cols[2].button("X", key=f"del{i}"):
        remove_pile(i)
        st.rerun()

st.sidebar.button("Add Pile", on_click=add_pile)

# --- Main Panel for Results ---
total_vertical_load = P + footing_self_weight
pile_loads = calculate_pile_loads(total_vertical_load, Mx, My, st.session_state.pile_coords)

# Display Results in a Table
st.header("Calculation Results")

results_df = pd.DataFrame({
    'Pile': [f"Pile {i+1}" for i in range(len(st.session_state.pile_coords))],
    'X-coordinate (m)': [f"{c[0]:.2f}" for c in st.session_state.pile_coords],
    'Y-coordinate (m)': [f"{c[1]:.2f}" for c in st.session_state.pile_coords],
    'Calculated Load (kN)': [f"{l:.2f}" for l in pile_loads]
})

st.dataframe(results_df.set_index('Pile'))

# Display Summary of Loads
st.subheader("Load Summary")
st.write(f"**Total Applied Vertical Load (P + Self-Weight):** {total_vertical_load:.2f} kN")
st.write(f"**Maximum Pile Load (Compression):** {max(pile_loads):.2f} kN")
st.write(f"**Minimum Pile Load (Tension/Uplift):** {min(pile_loads):.2f} kN")

# Visualization of Pile Loads
st.header("Pile Load Visualization")

if st.session_state.pile_coords:
    vis_df = pd.DataFrame({
        'x': [c[0] for c in st.session_state.pile_coords],
        'y': [c[1] for c in st.session_state.pile_coords],
        'load': pile_loads,
        'load_text': [f"{l:.1f} kN" for l in pile_loads]
    })

    # Determine the domain for the color scale to center on zero
    max_abs_load = max(abs(vis_df['load'].min()), abs(vis_df['load'].max()))

    # Calculate padding for the chart domain
    x_range = vis_df['x'].max() - vis_df['x'].min()
    y_range = vis_df['y'].max() - vis_df['y'].min()
    x_buffer = x_range * 0.2  # 20% buffer
    y_buffer = y_range * 0.2  # 20% buffer

    base_chart = alt.Chart(vis_df).encode(
        x=alt.X('x:Q', title='X-coordinate (m)',
                scale=alt.Scale(domain=[vis_df['x'].min() - x_buffer, vis_df['x'].max() + x_buffer]),
                axis=alt.Axis(titleFontSize=14, labelFontSize=12)),
        y=alt.Y('y:Q', title='Y-coordinate (m)',
                scale=alt.Scale(domain=[vis_df['y'].min() - y_buffer, vis_df['y'].max() + y_buffer]),
                axis=alt.Axis(titleFontSize=14, labelFontSize=12)),
        tooltip=[
            alt.Tooltip('x:Q', title='X-coordinate', format='.2f'),
            alt.Tooltip('y:Q', title='Y-coordinate', format='.2f'),
            alt.Tooltip('load:Q', title='Load (kN)', format='.2f')
        ]
    )

    # Points with color scale
    points = base_chart.mark_point(size=300, filled=True, stroke='black', strokeWidth=0.5).encode(
        color=alt.Color('load:Q', title='Load (kN)',
                        scale=alt.Scale(scheme='redblue', domain=[-max_abs_load, max_abs_load], reverse=True))
    )

    # Text labels for the loads
    text = base_chart.mark_text(align='center', dy=-15, fontSize=12).encode(
        text='load_text:N'
    )

    chart = (points + text).properties(
        title=alt.TitleParams(
            text='Pile Location and Load Distribution',
            subtitle='Red for Tension/Uplift, Blue for Compression',
            fontSize=20,
            subtitleFontSize=16
        ),
        padding={"left": 20, "top": 20, "right": 20, "bottom": 20} # Add padding
    ).interactive()

    st.altair_chart(chart, use_container_width=True)