Problem 9.26 - Beam Design
[Problem adapted from © Kurt Gramoll CC BY NC-SA 4.0]
#| standalone: true
#| viewerHeight: 600
#| components: [viewer]
from shiny import App, render, ui, reactive
import random
import asyncio
import io
import math
import string
import pandas as pd
from datetime import datetime
from pathlib import Path
def generate_random_letters(length):
# Generate a random string of letters of specified length
return "".join(random.choice(string.ascii_lowercase) for _ in range(length))
problem_ID = "367"
L = reactive.Value("__")
F = reactive.Value("__")
sigma_fail = reactive.Value("__")
attempts = ["Timestamp,Attempt,Answer,Feedback\n"]
attempts = ["Timestamp,Attempt,Answer1,Answer2,Feedback\n"]
app_ui = ui.page_fluid(
ui.markdown("**Please enter your ID number from your instructor and click to generate your problem**"),
ui.input_text("ID", "", placeholder="Enter ID Number Here"),
ui.input_action_button("generate_problem", "Generate Problem", class_="btn-primary"),
ui.markdown("**Problem Statement**"),
ui.output_ui("ui_problem_statement"),
ui.input_text("answer1", "Your Answer for the first beam number", placeholder="Please enter your answer 1"),
ui.input_text("answer2", "Your Answer for the second beam number", placeholder="Please enter your answer 2"),
ui.input_action_button("submit", "Submit Answers", class_="btn-primary"),
ui.download_button("download", "Download File to Submit", class_="btn-success"),
)
def server(input, output, session):
# Initialize a counter for attempts
attempt_counter = reactive.Value(0)
@output
@render.ui
def ui_problem_statement():
return [ui.markdown(f"A wide flange I-beam of length L = {L()} ft. supports a single load F = {F()} kips at its center. The failure stress is {sigma_fail()} ksi. Determine the lightest W-beam from Appendix A that could be used. Ignore the weight of the beam. ")]
@reactive.Effect
@reactive.event(input.generate_problem)
def randomize_vars():
random.seed(input.ID())
L.set(random.randrange(10,30,1))
F.set(random.randrange(10,50,1))
sigma_fail.set(random.randrange(15,25,1))
@reactive.Effect
@reactive.event(input.submit)
def _():
attempt_counter.set(
attempt_counter() + 1
) # Increment the attempt counter on each submission.
Ay = F()*L()*12/2/(L()*12)
M = Ay*L()/2*12
S = M/sigma_fail()
# Create a dataframe
df = pd.DataFrame()
# Define Beam Name Columns
df["I-Beam First Num"] = [40,40,40,36,36,36,30,30,30,24,24,24,21,21,21,18,18,18,16,16,16,14,14,14,12,12,12]
#Define Weight Column
df["Weight"] = [431,297,264,487,247,210,326,173,99,279,131,84,201,122,68,130,76,40,67,40,31,90,53,34,106,53,26]
# Define S Column
df["Sx (in^3)"] = [1690,1170,971,1830,913,719,1040,541,269,718,329,196,461,273,140,256,146,68.4,117,64.7,47.2,143,77.8,48.6,145,70.6,33.4]
# Create Solution Dataframe
instr=pd.DataFrame()
instr["First Num"] =[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
instr["Second Num"] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
# Search for Possible Solutions
i = 0
while i < 27:
if df.iloc[i, 2] >= S:
instr.iloc[i, 0] = df.iloc[i, 0]
instr.iloc[i, 1] = df.iloc[i, 1]
i += 1
instr = instr.loc[(instr != 0).any(axis=1)]
#Find Minimum Weight
wmin = min(instr.iloc[:,1])
mask = instr['Second Num'] == wmin
instr = pd.DataFrame(instr[mask])
correct1 = float(input.answer1()) == instr.iloc[0,0]
correct2 = float(input.answer2()) == instr.iloc[0,1]
if correct1 and correct2:
check = "both correct."
else:
check = f" {'correct' if correct1 else 'incorrect'} and {'correct' if correct2 else 'incorrect'}."
correct_indicator = "JL" if correct1 and correct2 else "JG"
random_start = generate_random_letters(4)
random_middle = generate_random_letters(4)
random_end = generate_random_letters(4)
encoded_attempt = f"{random_start}{problem_ID}-{random_middle}{attempt_counter()}{correct_indicator}-{random_end}{input.ID()}"
session.encoded_attempt = reactive.Value(encoded_attempt)
attempts.append(f"{datetime.now()}, {attempt_counter()}, {input.answer1()}, {input.answer2()}, {check}\n")
feedback = ui.markdown(f"Your answers of {input.answer1()} and {input.answer2()} are {check}. For reference in debugging this, the calculated instructor answers are {instr.iloc[0,0]} and {instr.iloc[0,1]}")
m = ui.modal(feedback, title="Feedback", easy_close=True)
ui.modal_show(m)
@session.download(filename=lambda: f"Problem_Log-{problem_ID}-{input.ID()}.csv")
async def download():
final_encoded = session.encoded_attempt() if session.encoded_attempt is not None else "No attempts"
yield f"{final_encoded}\n\n"
yield "Timestamp,Attempt,Answer1,Answer2,Feedback\n"
for attempt in attempts[1:]:
await asyncio.sleep(0.25)
yield attempt
app = App(app_ui, server)