jackvinati commited on
Commit
7a3a321
verified
1 Parent(s): 1932b27

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -160
app.py CHANGED
@@ -3,7 +3,7 @@ from pint import UnitRegistry, set_application_registry
3
  import matplotlib.pyplot as plt
4
  import io
5
  import base64
6
- from sympy import symbols, Symbol, Eq, solve, Rational
7
  from reportlab.lib.pagesizes import letter
8
  from reportlab.pdfgen import canvas
9
  from PIL import Image
@@ -12,7 +12,7 @@ import io
12
  from reportlab.lib.utils import ImageReader
13
  import os
14
  import subprocess
15
-
16
 
17
 
18
  # Initialize unit registry
@@ -67,35 +67,34 @@ def generate_beam_diagram(n_spans, lengths, loads, moments, R_sx, R_dx,
67
 
68
  total_length = x_positions[-1]
69
 
70
- fig, ax = plt.subplots(figsize=(10, 2))
71
-
72
- # Draw the beam as a line
73
  ax.hlines(0, 0, total_length, colors='black', linewidth=2)
74
 
75
- # Draw supports
 
 
76
  support_width = total_length / 50 # Adjust support width relative to total length
77
  n_supports = n_spans + 1
78
  support_positions = []
79
  idx = 0
80
  # Left support
81
  if cantilever_left_length.magnitude > 0:
82
- # Support at end of cantilever
83
  x = x_positions[1]
84
  support_positions.append(x)
85
- ax.plot([x, x], [0, -0.1], color='black')
86
- support = plt.Polygon([[x - support_width, -0.1],
87
- [x + support_width, -0.1],
88
- [x, -0.2]], color='black')
89
  ax.add_patch(support)
90
  idx = 2
91
  else:
92
- # Support at start
93
  x = x_positions[0]
94
  support_positions.append(x)
95
- ax.plot([x, x], [0, -0.1], color='black')
96
- support = plt.Polygon([[x - support_width, -0.1],
97
- [x + support_width, -0.1],
98
- [x, -0.2]], color='black')
99
  ax.add_patch(support)
100
  idx = 1
101
 
@@ -103,63 +102,54 @@ def generate_beam_diagram(n_spans, lengths, loads, moments, R_sx, R_dx,
103
  for i in range(1, n_supports - 1):
104
  x = x_positions[idx]
105
  support_positions.append(x)
106
- ax.plot([x, x], [0, -0.1], color='black')
107
- support = plt.Polygon([[x - support_width, -0.1],
108
- [x + support_width, -0.1],
109
- [x, -0.2]], color='black')
110
  ax.add_patch(support)
111
  idx += 1
112
 
113
  # Right support
114
  if cantilever_right_length.magnitude > 0:
115
- # Support before cantilever
116
  x = x_positions[-2]
117
  support_positions.append(x)
118
- ax.plot([x, x], [0, -0.1], color='black')
119
- support = plt.Polygon([[x - support_width, -0.1],
120
- [x + support_width, -0.1],
121
- [x, -0.2]], color='black')
122
  ax.add_patch(support)
123
  else:
124
- # Support at end
125
  x = x_positions[-1]
126
  support_positions.append(x)
127
- ax.plot([x, x], [0, -0.1], color='black')
128
- support = plt.Polygon([[x - support_width, -0.1],
129
- [x + support_width, -0.1],
130
- [x, -0.2]], color='black')
131
  ax.add_patch(support)
132
 
133
- # Annotate reactions
 
 
134
  for i, x in enumerate(support_positions):
135
- # Left-side reaction
136
- R_sx_value = R_sx[i]
137
- R_sx_unit = R_sx_value.to(reaction_unit)
138
- R_sx_num = round(R_sx_unit.magnitude, 6)
139
- # Right-side reaction
140
- R_dx_value = R_dx[i]
141
- R_dx_unit = R_dx_value.to(reaction_unit)
142
- R_dx_num = round(R_dx_unit.magnitude, 6)
143
- # Display reactions with proper offsets to avoid overlap
144
- ax.text(x, 0.15, f'$R_{{{i+1},\, \\mathrm{{sx}}}} = {R_sx_num}$ {reaction_unit_str}',
145
- ha='center', va='bottom', color='green')
146
- ax.text(x, 0.25, f'$R_{{{i+1},\, \\mathrm{{dx}}}} = {R_dx_num}$ {reaction_unit_str}',
147
- ha='center', va='bottom', color='green')
148
-
149
- # Annotate internal moments
150
  for i, x in enumerate(support_positions):
151
  M_value = moments[i]
152
  M_unit = M_value.to(moment_unit)
153
  M_num = round(M_unit.magnitude, 6)
154
  ax.text(x, -0.25, f'$M_{{{i+1}}} = {M_num}$ {moment_unit_str}',
155
- ha='center', va='top', color='blue')
156
 
157
- # Remove axes
158
  ax.axis('off')
159
  plt.xlim(-0.05 * total_length, total_length * 1.05)
160
  plt.ylim(-0.4, 0.4)
161
 
162
- # Save plot to a buffer
163
  buf = io.BytesIO()
164
  plt.savefig(buf, format='png', bbox_inches='tight', dpi=150)
165
  plt.close(fig)
@@ -171,69 +161,67 @@ def calculate_reactions(n_spans, lengths, distributed_loads, moments,
171
  cantilever_left_length=Q_(0.0, u.meter), cantilever_left_load=Q_(0.0, u.newton / u.meter),
172
  cantilever_right_length=Q_(0.0, u.meter), cantilever_right_load=Q_(0.0, u.newton / u.meter)):
173
  """
174
- Calculate reactions using Excel formulas and print torques:
175
- r1_sx = (sx cantilever * weight)
176
- r1_dx = (ln-1 * weight)/2 + (torque1 - torque2)/ln-1
177
- r2_sx = (ln-1 * weight) - r(m-1)_dx
178
- r2_dx = (ln-1 * weight)/2 + (torque_m - torque_m+1)/ln-1
179
- And so on...
180
  """
181
  n_supports = n_spans + 1
182
-
183
- # Print all torques (moments)
184
  print("\nTorques at each support:")
185
  for i, moment in enumerate(moments):
186
  print(f"Torque {i+1}: {moment}")
187
  print("\n")
188
-
189
- # Initialize reaction lists
190
  R_sx = [Q_(0.0, 'newton') for _ in range(n_supports)]
191
  R_dx = [Q_(0.0, 'newton') for _ in range(n_supports)]
192
 
193
  # ---------------------------
194
  # First support (m = 0)
195
  # ---------------------------
196
- print(f"Calculating R1:")
197
- # r1_sx = (sx cantilever * weight)
198
  if cantilever_left_length.magnitude > 0:
199
  R_sx[0] = cantilever_left_load * cantilever_left_length
200
- print(f"R1_sx = cantilever_load * cantilever_length = {R_sx[0]}")
201
 
202
- # r1_dx = (l0 * w0)/2 + (torque1 - torque2)/l0
203
- R_dx[0] = (distributed_loads[0] * lengths[0]) / 2 + \
204
- (moments[0] - moments[1]) / lengths[0]
205
  print(f"R1_dx = ({distributed_loads[0]} * {lengths[0]})/2 + ({moments[0]} - {moments[1]})/{lengths[0]} = {R_dx[0]}")
206
-
207
  # ---------------------------
208
- # Middle supports (1..n-1)
209
  # ---------------------------
210
  for m in range(1, n_supports - 1):
211
  print(f"\nCalculating R{m+1}:")
212
- # r(m)_sx = (l(m-1)*w(m-1)) - r(m-1)_dx
213
  R_sx[m] = distributed_loads[m-1] * lengths[m-1] - R_dx[m-1]
214
  print(f"R{m+1}_sx = {distributed_loads[m-1]} * {lengths[m-1]} - {R_dx[m-1]} = {R_sx[m]}")
215
 
216
- # r(m)_dx = (l(m)*w(m))/2 + (torque_m - torque_(m+1))/l(m)
217
- R_dx[m] = (distributed_loads[m] * lengths[m]) / 2 + \
218
- (moments[m] - moments[m+1]) / lengths[m]
219
  print(f"R{m+1}_dx = ({distributed_loads[m]} * {lengths[m]})/2 + ({moments[m]} - {moments[m+1]})/{lengths[m]} = {R_dx[m]}")
220
-
221
  # ---------------------------
222
- # Last support (m = n_supports-1)
223
  # ---------------------------
224
  m = n_supports - 1
225
  print(f"\nCalculating R{m+1}:")
226
-
227
- # Last left reaction
228
  R_sx[m] = distributed_loads[m-1] * lengths[m-1] - R_dx[m-1]
229
  print(f"R{m+1}_sx = {distributed_loads[m-1]} * {lengths[m-1]} - {R_dx[m-1]} = {R_sx[m]}")
230
 
231
- # --- Key Fix: Force R_dx at the last support to always be 0 ---
232
- R_dx[m] = Q_(0.0, 'newton')
233
- print(f"R{m+1}_dx is forced to 0: {R_dx[m]}")
234
- # --------------------------------------------------------------
 
 
 
 
235
 
236
- # Print final summary of all reactions
237
  print("\nFinal Reactions Summary:")
238
  for i in range(n_supports):
239
  print(f"R{i+1}_sx = {R_sx[i]}")
@@ -242,26 +230,23 @@ def calculate_reactions(n_spans, lengths, distributed_loads, moments,
242
 
243
  return R_sx, R_dx
244
 
245
-
246
-
247
-
248
  def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
249
- cantilever_left_length_str='0', cantilever_left_load_str='0',
250
- cantilever_right_length_str='0', cantilever_right_load_str='0'):
251
  # Parse the input strings into lists
252
  try:
253
  n_spans = int(n_spans)
254
  l_values = [float(val.strip()) for val in lengths_str.split(',')]
255
  p_values = [float(val.strip()) for val in loads_str.split(',')]
256
- cantilever_left_length = float(cantilever_left_length_str.strip())
257
- cantilever_left_load = float(cantilever_left_load_str.strip())
258
- cantilever_right_length = float(cantilever_right_length_str.strip())
259
- cantilever_right_load = float(cantilever_right_load_str.strip())
260
  except ValueError:
261
- return "Invalid input. Please ensure all inputs are numbers.", "", "", "", "", ""
262
 
263
  if len(l_values) != n_spans or len(p_values) != n_spans:
264
- return "The number of lengths and loads must match the number of spans.", "", "", "", "", ""
265
 
266
  n_supports = n_spans + 1 # Total number of supports
267
 
@@ -286,12 +271,12 @@ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
286
  p_SI = [load.to(u.newton / u.meter) for load in p]
287
 
288
  # Process cantilever inputs
289
- cantilever_left_length = Q_(cantilever_left_length, length_unit)
290
- cantilever_left_load = Q_(cantilever_left_load, load_unit)
291
- cantilever_right_length = Q_(cantilever_right_length, length_unit)
292
- cantilever_right_load = Q_(cantilever_right_load, load_unit)
293
 
294
- # Convert to SI units for calculations
295
  cantilever_left_length_SI = cantilever_left_length.to(u.meter)
296
  cantilever_left_load_SI = cantilever_left_load.to(u.newton / u.meter)
297
  cantilever_right_length_SI = cantilever_right_length.to(u.meter)
@@ -303,7 +288,7 @@ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
303
 
304
  # Handle single-span beam separately
305
  if n_spans == 1:
306
- # Moments at supports A and B for a single span
307
  M_cantilever_left = 0.0
308
  if cantilever_left_length_SI.magnitude > 0 and cantilever_left_load_SI.magnitude != 0:
309
  M_cantilever_left = (cantilever_left_load_SI * cantilever_left_length_SI**2 / 2).to(u.newton * u.meter).magnitude
@@ -316,27 +301,24 @@ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
316
  M_A = M_cantilever_left
317
  M_B = M_cantilever_right
318
 
319
- # Store the moments in lists
320
  M_values_SI = [Q_(M_A, u.newton * u.meter), Q_(M_B, u.newton * u.meter)]
321
- M_values_Imperial = [M.to(u.pound_force * u.foot) for M in M_values_SI]
322
 
323
- # Calculate reactions
324
- R_sx_SI, R_dx_SI = calculate_reactions(n_spans, l_SI, p_SI, M_values_SI)
 
 
 
325
 
326
- # Convert reactions to Imperial units
327
  R_sx_Imperial = [R.to(u.pound_force) for R in R_sx_SI]
328
  R_dx_Imperial = [R.to(u.pound_force) for R in R_dx_SI]
329
 
330
- # Prepare results
331
  results_SI = ""
332
  results_Imperial = ""
333
  for i in range(n_supports):
334
- M_value_SI = M_values_SI[i]
335
- M_value_Imperial = M_values_Imperial[i]
336
- results_SI += f"M_{i+1} = {M_value_SI.magnitude:.6f} N路m\n"
337
- results_Imperial += f"M_{i+1} = {M_value_Imperial.magnitude:.6f} lb路ft\n"
338
 
339
- # Generate beam diagrams for SI and Imperial units
340
  beam_diagram_SI = generate_beam_diagram(
341
  n_spans, l, p, M_values_SI, R_sx_SI, R_dx_SI,
342
  cantilever_left_length=cantilever_left_length,
@@ -353,10 +335,9 @@ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
353
  cantilever_right_load=cantilever_right_load,
354
  unit_system='Imperial'
355
  )
356
-
357
- # There are no complex equations for the single-span beam, so leave the equations blank.
358
  equations_md = ""
359
- return results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md, ""
360
 
361
  # For multiple spans
362
  else:
@@ -369,7 +350,7 @@ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
369
  if cantilever_right_length_SI.magnitude > 0 and cantilever_right_load_SI.magnitude != 0:
370
  M_cantilever_right = (cantilever_right_load_SI * cantilever_right_length_SI**2 / 2).to(u.newton * u.meter).magnitude
371
 
372
- # Initialize moments M_i (M_1 to M_{n_supports})
373
  M_symbols = []
374
  M_symbols.append(M_cantilever_left) # M_1
375
 
@@ -378,42 +359,31 @@ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
378
 
379
  M_symbols.append(M_cantilever_right) # M_n
380
 
381
- # Set up the system of equations
382
  equations = []
383
  equations_latex = []
384
  for k in range(1, n_supports - 1):
385
- # Indices for spans and supports
386
- l_prev = l_SI[k - 1].magnitude # l_{k} in meters
387
- l_curr = l_SI[k].magnitude # l_{k+1} in meters
388
- p_prev = p_SI[k - 1].magnitude # p_{k} in N/m
389
- p_curr = p_SI[k].magnitude # p_{k+1} in N/m
390
- M_prev = M_symbols[k - 1] # M_{k}
391
- M_curr = M_symbols[k] # M_{k+1}
392
- M_next = M_symbols[k + 1] # M_{k+2}
393
-
394
- # Left-hand side of the equation (N路m虏)
395
- lhs = (1/24) * (l_prev**3 * p_prev + l_curr**3 * p_curr)
396
 
397
- # Right-hand side of the equation (N路m虏)
398
  rhs = (1/6) * (l_prev * M_prev + l_curr * M_next) + (1/3) * (l_prev + l_curr) * M_curr
399
-
400
- # Form the equation
401
  equation = Eq(lhs, rhs)
402
  equations.append(equation)
403
-
404
- # Convert equation to LaTeX for display
405
  equation_latex = f"\\frac{{1}}{{24}}(l_{{{k}}}^3 p_{{{k}}} + l_{{{k+1}}}^3 p_{{{k+1}}}) = \\frac{{1}}{{6}}(l_{{{k}}} M_{{{k}}} + l_{{{k+1}}} M_{{{k+2}}}) + \\frac{{1}}{{3}}(l_{{{k}}} + l_{{{k+1}}}) M_{{{k+1}}}"
406
  equations_latex.append(equation_latex)
407
 
408
- # Solve the system of equations
409
- unknown_M_symbols = [M_symbols[i] for i in range(
410
- 1, n_supports - 1) if isinstance(M_symbols[i], Symbol)]
411
-
412
  solution = solve(equations, unknown_M_symbols, dict=True)
413
 
414
- # Prepare results and collect moments for diagrams
415
  if solution:
416
- solution = solution[0] # Extract the solution dictionary
417
  results_SI = ""
418
  results_Imperial = ""
419
  M_values_SI = []
@@ -424,26 +394,28 @@ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
424
  M_i_value = float(solution.get(M_i_value, 0))
425
  else:
426
  M_i_value = float(M_i_value)
427
-
428
- # SI Units
429
  M_quantity_SI = Q_(M_i_value, u.newton * u.meter)
430
  M_values_SI.append(M_quantity_SI)
431
  results_SI += f"M_{i+1} = {M_quantity_SI.magnitude:.6f} N路m\n"
432
- # Imperial Units
433
  M_quantity_Imperial = M_quantity_SI.to(u.pound_force * u.foot)
434
  M_values_Imperial.append(M_quantity_Imperial)
435
  results_Imperial += f"M_{i+1} = {M_quantity_Imperial.magnitude:.6f} lb路ft\n"
436
  else:
437
- return "No solution found.", "", "", "", "", ""
438
 
439
  # Calculate reactions
440
- R_sx_SI, R_dx_SI = calculate_reactions(n_spans, l_SI, p_SI, M_values_SI)
 
 
 
 
 
 
 
441
 
442
- # Convert reactions to Imperial units
443
  R_sx_Imperial = [R.to(u.pound_force) for R in R_sx_SI]
444
  R_dx_Imperial = [R.to(u.pound_force) for R in R_dx_SI]
445
 
446
- # Generate beam diagrams
447
  beam_diagram_SI = generate_beam_diagram(
448
  n_spans, l, p, M_values_SI, R_sx_SI, R_dx_SI,
449
  cantilever_left_length=cantilever_left_length,
@@ -459,50 +431,66 @@ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
459
  cantilever_right_load=cantilever_right_load,
460
  unit_system='Imperial')
461
 
462
- # Prepare equations in LaTeX
463
  equations_md = "\n\n".join(
464
- [f"**Equation {i+1}:**\n\n$$ {eq} $$" for i, eq in enumerate(equations_latex)])
 
 
465
 
466
- return results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md, ""
467
 
 
468
 
469
  def gradio_interface(unit_system, n_spans, lengths_str, loads_str,
470
  cantilever_left_length, cantilever_left_load,
471
  cantilever_right_length, cantilever_right_load):
472
- # Call continuous beam solver to get results
473
- results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md, pdf_filename = continuous_beam_solver(
 
474
  unit_system, n_spans, lengths_str, loads_str,
475
  cantilever_left_length, cantilever_left_load,
476
  cantilever_right_length, cantilever_right_load)
 
 
477
 
478
- return results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md
479
 
 
 
480
  iface = gr.Interface(
481
  fn=gradio_interface,
482
  inputs=[
483
  gr.Radio(['SI', 'Imperial'], label="Unit System", value='SI'),
484
  gr.Number(label="Number of Spans (n)", value=3, precision=0),
485
- gr.Textbox(label="Lengths l_i (comma-separated)", placeholder="e.g., 5, 5, 5", value='7.91667,7.91667,7.91667'),
486
- gr.Textbox(label="Loads p_i (comma-separated)", placeholder="e.g., 10000, 10000, 10000", value='200,200,200'),
487
- gr.Textbox(label="Cantilever Left Length", placeholder="e.g., 2.0", value='6.66667'),
488
- gr.Textbox(label="Cantilever Left Load", placeholder="e.g., 5000", value='200'),
489
- gr.Textbox(label="Cantilever Right Length", placeholder="e.g., 0", value='6.66667'),
490
- gr.Textbox(label="Cantilever Right Load", placeholder="e.g., 0", value='200'),
 
 
 
 
 
 
491
  ],
492
  outputs=[
 
493
  gr.Textbox(label="Internal Moments at Supports (SI Units)"),
494
  gr.Textbox(label="Internal Moments at Supports (Imperial Units)"),
495
  gr.HTML(label="Beam Diagram (SI Units)"),
496
  gr.HTML(label="Beam Diagram (Imperial Units)"),
497
- gr.Markdown(label="Equations Used")
498
  ],
499
  title="Continuous Beam Solver with Cantilevers",
500
- description="Solve for internal moments at supports of a continuous beam with multiple spans, including cantilevers.\n"
501
- "Select the unit system for input. The results will be displayed in both SI and Imperial units.\n\n"
502
- "The beam diagrams and equations used will be displayed below.",
 
 
 
 
 
503
  allow_flagging="never",
504
  )
505
 
506
-
507
  if __name__ == "__main__":
508
- iface.launch()
 
3
  import matplotlib.pyplot as plt
4
  import io
5
  import base64
6
+ from sympy import symbols, Symbol, Eq, solve
7
  from reportlab.lib.pagesizes import letter
8
  from reportlab.pdfgen import canvas
9
  from PIL import Image
 
12
  from reportlab.lib.utils import ImageReader
13
  import os
14
  import subprocess
15
+ import contextlib
16
 
17
 
18
  # Initialize unit registry
 
67
 
68
  total_length = x_positions[-1]
69
 
70
+ fig, ax = plt.subplots(figsize=(10, 3))
71
+ # Draw the beam as a horizontal line
 
72
  ax.hlines(0, 0, total_length, colors='black', linewidth=2)
73
 
74
+ # -------------------------------------------
75
+ # Draw supports (rotated 180掳 so the tip is on the beam)
76
+ # -------------------------------------------
77
  support_width = total_length / 50 # Adjust support width relative to total length
78
  n_supports = n_spans + 1
79
  support_positions = []
80
  idx = 0
81
  # Left support
82
  if cantilever_left_length.magnitude > 0:
83
+ # Support at the end of the left cantilever (x_positions[1])
84
  x = x_positions[1]
85
  support_positions.append(x)
86
+ support = plt.Polygon([[x - support_width, -0.2],
87
+ [x + support_width, -0.2],
88
+ [x, 0]], color='black')
 
89
  ax.add_patch(support)
90
  idx = 2
91
  else:
92
+ # Support at start (x_positions[0])
93
  x = x_positions[0]
94
  support_positions.append(x)
95
+ support = plt.Polygon([[x - support_width, -0.2],
96
+ [x + support_width, -0.2],
97
+ [x, 0]], color='black')
 
98
  ax.add_patch(support)
99
  idx = 1
100
 
 
102
  for i in range(1, n_supports - 1):
103
  x = x_positions[idx]
104
  support_positions.append(x)
105
+ support = plt.Polygon([[x - support_width, -0.2],
106
+ [x + support_width, -0.2],
107
+ [x, 0]], color='black')
 
108
  ax.add_patch(support)
109
  idx += 1
110
 
111
  # Right support
112
  if cantilever_right_length.magnitude > 0:
113
+ # Support before the right cantilever (x_positions[-2])
114
  x = x_positions[-2]
115
  support_positions.append(x)
116
+ support = plt.Polygon([[x - support_width, -0.2],
117
+ [x + support_width, -0.2],
118
+ [x, 0]], color='black')
 
119
  ax.add_patch(support)
120
  else:
121
+ # Support at end (x_positions[-1])
122
  x = x_positions[-1]
123
  support_positions.append(x)
124
+ support = plt.Polygon([[x - support_width, -0.2],
125
+ [x + support_width, -0.2],
126
+ [x, 0]], color='black')
 
127
  ax.add_patch(support)
128
 
129
+ # -------------------------------------------
130
+ # Annotate the TOTAL reaction force (sum of left and right) at each support
131
+ # -------------------------------------------
132
  for i, x in enumerate(support_positions):
133
+ R_total = R_sx[i] + R_dx[i]
134
+ R_total_conv = R_total.to(reaction_unit)
135
+ R_total_num = round(R_total_conv.magnitude, 6)
136
+ ax.text(x, 0.25, f'$R_{{{i+1}}} = {R_total_num}$ {reaction_unit_str}',
137
+ ha='center', va='bottom', color='green', fontsize=10)
138
+
139
+ # Annotate internal moments below the beam
 
 
 
 
 
 
 
 
140
  for i, x in enumerate(support_positions):
141
  M_value = moments[i]
142
  M_unit = M_value.to(moment_unit)
143
  M_num = round(M_unit.magnitude, 6)
144
  ax.text(x, -0.25, f'$M_{{{i+1}}} = {M_num}$ {moment_unit_str}',
145
+ ha='center', va='top', color='blue', fontsize=10)
146
 
147
+ # Remove axes and adjust limits
148
  ax.axis('off')
149
  plt.xlim(-0.05 * total_length, total_length * 1.05)
150
  plt.ylim(-0.4, 0.4)
151
 
152
+ # Save plot to a buffer and encode as base64
153
  buf = io.BytesIO()
154
  plt.savefig(buf, format='png', bbox_inches='tight', dpi=150)
155
  plt.close(fig)
 
161
  cantilever_left_length=Q_(0.0, u.meter), cantilever_left_load=Q_(0.0, u.newton / u.meter),
162
  cantilever_right_length=Q_(0.0, u.meter), cantilever_right_load=Q_(0.0, u.newton / u.meter)):
163
  """
164
+ Calculate reactions.
165
+ For a cantilever the reaction force is simply:
166
+ R = (distributed load) * (cantilever length)
 
 
 
167
  """
168
  n_supports = n_spans + 1
169
+
170
+ # Print all torques (moments) at supports
171
  print("\nTorques at each support:")
172
  for i, moment in enumerate(moments):
173
  print(f"Torque {i+1}: {moment}")
174
  print("\n")
175
+
176
+ # Initialize reaction lists (each support will have left (R_sx) and right (R_dx) components)
177
  R_sx = [Q_(0.0, 'newton') for _ in range(n_supports)]
178
  R_dx = [Q_(0.0, 'newton') for _ in range(n_supports)]
179
 
180
  # ---------------------------
181
  # First support (m = 0)
182
  # ---------------------------
183
+ print("Calculating R1:")
184
+ # For a left cantilever, the left reaction is the cantilever force:
185
  if cantilever_left_length.magnitude > 0:
186
  R_sx[0] = cantilever_left_load * cantilever_left_length
187
+ print(f"R1_sx = cantilever_left_load * cantilever_left_length = {R_sx[0]}")
188
 
189
+ # r1_dx = (l0 * w0)/2 + (M1 - M2)/l0
190
+ R_dx[0] = (distributed_loads[0] * lengths[0]) / 2 + (moments[0] - moments[1]) / lengths[0]
 
191
  print(f"R1_dx = ({distributed_loads[0]} * {lengths[0]})/2 + ({moments[0]} - {moments[1]})/{lengths[0]} = {R_dx[0]}")
192
+
193
  # ---------------------------
194
+ # Middle supports (1 .. n-1)
195
  # ---------------------------
196
  for m in range(1, n_supports - 1):
197
  print(f"\nCalculating R{m+1}:")
198
+ # Left reaction at support m: load from previous span minus the right reaction of previous support
199
  R_sx[m] = distributed_loads[m-1] * lengths[m-1] - R_dx[m-1]
200
  print(f"R{m+1}_sx = {distributed_loads[m-1]} * {lengths[m-1]} - {R_dx[m-1]} = {R_sx[m]}")
201
 
202
+ # Right reaction at support m: half the load on the next span plus moment difference contribution
203
+ R_dx[m] = (distributed_loads[m] * lengths[m]) / 2 + (moments[m] - moments[m+1]) / lengths[m]
 
204
  print(f"R{m+1}_dx = ({distributed_loads[m]} * {lengths[m]})/2 + ({moments[m]} - {moments[m+1]})/{lengths[m]} = {R_dx[m]}")
205
+
206
  # ---------------------------
207
+ # Last support (m = n_supports - 1)
208
  # ---------------------------
209
  m = n_supports - 1
210
  print(f"\nCalculating R{m+1}:")
211
+ # Left reaction at last support from the previous span
 
212
  R_sx[m] = distributed_loads[m-1] * lengths[m-1] - R_dx[m-1]
213
  print(f"R{m+1}_sx = {distributed_loads[m-1]} * {lengths[m-1]} - {R_dx[m-1]} = {R_sx[m]}")
214
 
215
+ # For a right cantilever, the right reaction is the cantilever force;
216
+ # otherwise it is set to zero.
217
+ if cantilever_right_length.magnitude > 0:
218
+ R_dx[m] = cantilever_right_load * cantilever_right_length
219
+ print(f"R{m+1}_dx = cantilever_right_load * cantilever_right_length = {R_dx[m]}")
220
+ else:
221
+ R_dx[m] = Q_(0.0, 'newton')
222
+ print(f"R{m+1}_dx is forced to 0: {R_dx[m]}")
223
 
224
+ # Print final reactions summary
225
  print("\nFinal Reactions Summary:")
226
  for i in range(n_supports):
227
  print(f"R{i+1}_sx = {R_sx[i]}")
 
230
 
231
  return R_sx, R_dx
232
 
 
 
 
233
  def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
234
+ cantilever_left_length_str='0', cantilever_left_load_str='0',
235
+ cantilever_right_length_str='0', cantilever_right_load_str='0'):
236
  # Parse the input strings into lists
237
  try:
238
  n_spans = int(n_spans)
239
  l_values = [float(val.strip()) for val in lengths_str.split(',')]
240
  p_values = [float(val.strip()) for val in loads_str.split(',')]
241
+ cant_left_len_val = float(cantilever_left_length_str.strip())
242
+ cant_left_load_val = float(cantilever_left_load_str.strip())
243
+ cant_right_len_val = float(cantilever_right_length_str.strip())
244
+ cant_right_load_val = float(cantilever_right_load_str.strip())
245
  except ValueError:
246
+ return "Invalid input. Please ensure all inputs are numbers.", "", "", "", ""
247
 
248
  if len(l_values) != n_spans or len(p_values) != n_spans:
249
+ return "The number of lengths and loads must match the number of spans.", "", "", "", ""
250
 
251
  n_supports = n_spans + 1 # Total number of supports
252
 
 
271
  p_SI = [load.to(u.newton / u.meter) for load in p]
272
 
273
  # Process cantilever inputs
274
+ cantilever_left_length = Q_(cant_left_len_val, length_unit)
275
+ cantilever_left_load = Q_(cant_left_load_val, load_unit)
276
+ cantilever_right_length = Q_(cant_right_len_val, length_unit)
277
+ cantilever_right_load = Q_(cant_right_load_val, load_unit)
278
 
279
+ # Convert cantilever inputs to SI units for calculations
280
  cantilever_left_length_SI = cantilever_left_length.to(u.meter)
281
  cantilever_left_load_SI = cantilever_left_load.to(u.newton / u.meter)
282
  cantilever_right_length_SI = cantilever_right_length.to(u.meter)
 
288
 
289
  # Handle single-span beam separately
290
  if n_spans == 1:
291
+ # Cantilever moment contributions (if any)
292
  M_cantilever_left = 0.0
293
  if cantilever_left_length_SI.magnitude > 0 and cantilever_left_load_SI.magnitude != 0:
294
  M_cantilever_left = (cantilever_left_load_SI * cantilever_left_length_SI**2 / 2).to(u.newton * u.meter).magnitude
 
301
  M_A = M_cantilever_left
302
  M_B = M_cantilever_right
303
 
 
304
  M_values_SI = [Q_(M_A, u.newton * u.meter), Q_(M_B, u.newton * u.meter)]
305
+ M_values_Imperial = [M_values_SI[0].to(u.pound_force * u.foot), M_values_SI[1].to(u.pound_force * u.foot)]
306
 
307
+ # Calculate reactions+
308
+ f = io.StringIO()
309
+ with contextlib.redirect_stdout(f):
310
+ R_sx_SI, R_dx_SI = calculate_reactions(n_spans, l_SI, p_SI, M_values_SI)
311
+ reaction_log = f.getvalue()
312
 
 
313
  R_sx_Imperial = [R.to(u.pound_force) for R in R_sx_SI]
314
  R_dx_Imperial = [R.to(u.pound_force) for R in R_dx_SI]
315
 
 
316
  results_SI = ""
317
  results_Imperial = ""
318
  for i in range(n_supports):
319
+ results_SI += f"M_{i+1} = {M_values_SI[i].magnitude:.6f} N路m\n"
320
+ results_Imperial += f"M_{i+1} = {M_values_Imperial[i].magnitude:.6f} lb路ft\n"
 
 
321
 
 
322
  beam_diagram_SI = generate_beam_diagram(
323
  n_spans, l, p, M_values_SI, R_sx_SI, R_dx_SI,
324
  cantilever_left_length=cantilever_left_length,
 
335
  cantilever_right_load=cantilever_right_load,
336
  unit_system='Imperial'
337
  )
338
+ # For single-span, no complex equations need to be shown.
 
339
  equations_md = ""
340
+ return equations_md, results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, reaction_log
341
 
342
  # For multiple spans
343
  else:
 
350
  if cantilever_right_length_SI.magnitude > 0 and cantilever_right_load_SI.magnitude != 0:
351
  M_cantilever_right = (cantilever_right_load_SI * cantilever_right_length_SI**2 / 2).to(u.newton * u.meter).magnitude
352
 
353
+ # Initialize moments M_i (M_1 to M_n)
354
  M_symbols = []
355
  M_symbols.append(M_cantilever_left) # M_1
356
 
 
359
 
360
  M_symbols.append(M_cantilever_right) # M_n
361
 
362
+ # Set up the system of equations (for supports 2 to n-1)
363
  equations = []
364
  equations_latex = []
365
  for k in range(1, n_supports - 1):
366
+ l_prev = l_SI[k - 1].magnitude
367
+ l_curr = l_SI[k].magnitude
368
+ p_prev = p_SI[k - 1].magnitude
369
+ p_curr = p_SI[k].magnitude
370
+ M_prev = M_symbols[k - 1]
371
+ M_curr = M_symbols[k]
372
+ M_next = M_symbols[k + 1]
 
 
 
 
373
 
374
+ lhs = (1/24) * (l_prev**3 * p_prev + l_curr**3 * p_curr)
375
  rhs = (1/6) * (l_prev * M_prev + l_curr * M_next) + (1/3) * (l_prev + l_curr) * M_curr
 
 
376
  equation = Eq(lhs, rhs)
377
  equations.append(equation)
 
 
378
  equation_latex = f"\\frac{{1}}{{24}}(l_{{{k}}}^3 p_{{{k}}} + l_{{{k+1}}}^3 p_{{{k+1}}}) = \\frac{{1}}{{6}}(l_{{{k}}} M_{{{k}}} + l_{{{k+1}}} M_{{{k+2}}}) + \\frac{{1}}{{3}}(l_{{{k}}} + l_{{{k+1}}}) M_{{{k+1}}}"
379
  equations_latex.append(equation_latex)
380
 
381
+ # Solve the system for the unknown moments
382
+ unknown_M_symbols = [M_symbols[i] for i in range(1, n_supports - 1) if isinstance(M_symbols[i], Symbol)]
 
 
383
  solution = solve(equations, unknown_M_symbols, dict=True)
384
 
 
385
  if solution:
386
+ solution = solution[0]
387
  results_SI = ""
388
  results_Imperial = ""
389
  M_values_SI = []
 
394
  M_i_value = float(solution.get(M_i_value, 0))
395
  else:
396
  M_i_value = float(M_i_value)
 
 
397
  M_quantity_SI = Q_(M_i_value, u.newton * u.meter)
398
  M_values_SI.append(M_quantity_SI)
399
  results_SI += f"M_{i+1} = {M_quantity_SI.magnitude:.6f} N路m\n"
 
400
  M_quantity_Imperial = M_quantity_SI.to(u.pound_force * u.foot)
401
  M_values_Imperial.append(M_quantity_Imperial)
402
  results_Imperial += f"M_{i+1} = {M_quantity_Imperial.magnitude:.6f} lb路ft\n"
403
  else:
404
+ return "No solution found.", "", "", "", ""
405
 
406
  # Calculate reactions
407
+ f = io.StringIO()
408
+ with contextlib.redirect_stdout(f):
409
+ R_sx_SI, R_dx_SI = calculate_reactions(n_spans, l_SI, p_SI, M_values_SI,
410
+ cantilever_left_length=cantilever_left_length_SI,
411
+ cantilever_left_load=cantilever_left_load_SI,
412
+ cantilever_right_length=cantilever_right_length_SI,
413
+ cantilever_right_load=cantilever_right_load_SI)
414
+ reaction_log = f.getvalue()
415
 
 
416
  R_sx_Imperial = [R.to(u.pound_force) for R in R_sx_SI]
417
  R_dx_Imperial = [R.to(u.pound_force) for R in R_dx_SI]
418
 
 
419
  beam_diagram_SI = generate_beam_diagram(
420
  n_spans, l, p, M_values_SI, R_sx_SI, R_dx_SI,
421
  cantilever_left_length=cantilever_left_length,
 
431
  cantilever_right_load=cantilever_right_load,
432
  unit_system='Imperial')
433
 
 
434
  equations_md = "\n\n".join(
435
+ [f"**Equation {i+1}:**\n\n$$ {eq} $$" for i, eq in enumerate(equations_latex)]
436
+ )
437
+ return equations_md, results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, reaction_log
438
 
 
439
 
440
+
441
 
442
  def gradio_interface(unit_system, n_spans, lengths_str, loads_str,
443
  cantilever_left_length, cantilever_left_load,
444
  cantilever_right_length, cantilever_right_load):
445
+ # Call the continuous beam solver to obtain all outputs including the reaction log.
446
+ (equations_md, results_SI, results_Imperial,
447
+ beam_diagram_SI, beam_diagram_Imperial, reaction_log) = continuous_beam_solver(
448
  unit_system, n_spans, lengths_str, loads_str,
449
  cantilever_left_length, cantilever_left_load,
450
  cantilever_right_length, cantilever_right_load)
451
+ # Return outputs in the new order: equations, moments, diagrams, and then the reaction log.
452
+ return equations_md, results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, reaction_log
453
 
 
454
 
455
+ # Build the Gradio interface.
456
+ # Note that the input labels now indicate the expected units.
457
  iface = gr.Interface(
458
  fn=gradio_interface,
459
  inputs=[
460
  gr.Radio(['SI', 'Imperial'], label="Unit System", value='SI'),
461
  gr.Number(label="Number of Spans (n)", value=3, precision=0),
462
+ gr.Textbox(label="Lengths l_i (comma-separated) [m (SI) or ft (Imperial)]",
463
+ placeholder="e.g., 7.92, 7.92, 7.92", value='7.91667,7.91667,7.91667'),
464
+ gr.Textbox(label="Loads p_i (comma-separated) [N/m (SI) or lb/ft (Imperial)]",
465
+ placeholder="e.g., 200,200,200", value='200,200,200'),
466
+ gr.Textbox(label="Cantilever Left Length [m (SI) or ft (Imperial)]",
467
+ placeholder="e.g., 6.67", value='6.66667'),
468
+ gr.Textbox(label="Cantilever Left Load [N/m (SI) or lb/ft (Imperial)]",
469
+ placeholder="e.g., 200", value='200'),
470
+ gr.Textbox(label="Cantilever Right Length [m (SI) or ft (Imperial)]",
471
+ placeholder="e.g., 6.67", value='6.66667'),
472
+ gr.Textbox(label="Cantilever Right Load [N/m (SI) or lb/ft (Imperial)]",
473
+ placeholder="e.g., 200", value='200'),
474
  ],
475
  outputs=[
476
+ gr.Markdown(label="Equations Used"),
477
  gr.Textbox(label="Internal Moments at Supports (SI Units)"),
478
  gr.Textbox(label="Internal Moments at Supports (Imperial Units)"),
479
  gr.HTML(label="Beam Diagram (SI Units)"),
480
  gr.HTML(label="Beam Diagram (Imperial Units)"),
481
+ gr.Textbox(label="Reactions Calculation Log"),
482
  ],
483
  title="Continuous Beam Solver with Cantilevers",
484
+ description=(
485
+ "Solve for internal moments at supports of a continuous beam with multiple spans, including cantilevers.\n\n"
486
+ "**Input Units:**\n"
487
+ "- For SI: Lengths in meters (m), Loads in Newtons per meter (N/m), Moments in N路m, Reaction forces in N.\n"
488
+ "- For Imperial: Lengths in feet (ft), Loads in pounds per foot (lb/ft), Moments in lb路ft, Reaction forces in lb.\n\n"
489
+ "The outputs are arranged with the equations on top, followed by the resulting internal moments, "
490
+ "then the beam diagram, and finally the complete reaction calculation log."
491
+ ),
492
  allow_flagging="never",
493
  )
494
 
 
495
  if __name__ == "__main__":
496
+ iface.launch()