Problem 15.14 - Effect of Supports

A column of length L is fixed at both ends and is subjected to a force F. [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 = "624"
F = reactive.Value("__")
L = reactive.Value("__")
FS = reactive.Value("__")

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.row(
        ui.column(12, ui.div(
            ui.row(
                ui.column(1, ui.tags.label("W", class_="form-label", style="line-height: 38px;")),
                ui.column(3, ui.input_text("answer1", "", placeholder="Enter your answer 1")),
                ui.column(1, ui.tags.label("x", class_="form-label", style="line-height: 38px; margin-left: 10px;")),
                ui.column(3, ui.input_text("answer2", "", placeholder="Enter your answer 2")),
            ),
            class_="d-flex justify-content-center align-items-center"
        ))
    ),  
    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 column that is fixed at both ends is required to carry a load F = {F()} kips. If length L = {L()} ft and the elastic modulus E = 29,000 ksi, identify the lightest structural member in Appendix A that can be used. Use a factor of safety of {FS()} against buckling.\n\n Enter two numbers below such that the answer is in the form W 24 x 16."
            )
        ]

    @reactive.Effect
    @reactive.event(input.generate_problem)
    def randomize_vars():
        random.seed(input.ID())
        F.set(random.randrange(300, 800, 10))
        L.set(random.randrange(150, 300, 1) / 10)
        FS.set(random.randrange(15, 40, 1) / 10)

    @reactive.Effect
    @reactive.event(input.submit)
    def _():
        attempt_counter.set(
            attempt_counter() + 1
        )  # Increment the attempt counter on each submission.
        I = F()*L()**2*144*FS()/(4*math.pi**2*29000)

        # 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 Iy Column
        df["Iy (in\u2074)"] = [1690,1090,493,2250,1010,411,1240,598,128,823,340,94.4,542,305,64.7,278,152,19.1,119,28.9,12.4,362,57.7,23.3,301,95.8,17.3]

        # 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]
        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]
        
        # Search for Possible Solutions
        i = 0
        while i < 27:
            if df.iloc[i, 2] >= I:
                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}.")
        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)