Problem 11.35 - By Superposition
[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
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="434"
L=reactive.Value("__")
delta=reactive.Value("__")
W=reactive.Value("__")
E=reactive.Value("__")
attempts=["Timestamp,Attempt,Answer,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("answer","Your Answer in numbers of boards", placeholder="Please enter your answer"),
ui.input_action_button("submit", "Submit Answer", 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 volcano has erupted and you need to cross a span of L = {L()} ft with lava only δ = {delta()} in. below. You have a stack of 2x4's (actual dimensions 3.5 in. by 1.5 in.). If the boards are placed flat, determine the minimum number of boards you need to to stack to prevent contact with the lava as you cross. Assume you weigh W = {W()} lb, E = {E()} ksi, and you may neglect the weight of the boards. [Hint: The maximum deflection will occur when you are standing at the center of the span].")]
@reactive.Effect
@reactive.event(input.generate_problem)
def randomize_vars():
random.seed(input.ID())
L.set(random.randrange(20,40,1))
delta.set(random.randrange(30,80,1)/10)
W.set(random.randrange(140,225,1))
E.set(random.randrange(7500,12500,100))
@reactive.Effect
@reactive.event(input.submit)
def _():
attempt_counter.set(attempt_counter() + 1) # Increment the attempt counter on each submission.
I = W()*L()**3/(48*E()*delta())
a = (12*I/3.5)**(1/3)
instr = math.ceil(a/1.5)
if math.isclose(float(input.answer()), instr, rel_tol=0.01):
check = "*Correct*"
correct_indicator = "JL"
else:
check = "*Not Correct.*"
correct_indicator = "JG"
# Generate random parts for the encoded attempt.
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()}"
# Store the most recent encoded attempt in a reactive value so it persists across submissions
session.encoded_attempt = reactive.Value(encoded_attempt)
# Append the attempt data to the attempts list without the encoded attempt
attempts.append(f"{datetime.now()}, {attempt_counter()}, {input.answer()}, {check}\n")
# Show feedback to the user.
feedback = ui.markdown(f"Your answer of {input.answer()} is {check}. For reference in debugging this, the calculated instructor answer is {instr}")
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():
# Start the CSV with the encoded attempt (without label)
final_encoded = session.encoded_attempt() if session.encoded_attempt is not None else "No attempts"
yield f"{final_encoded}\n\n"
# Write the header for the remaining CSV data once
yield "Timestamp,Attempt,Answer,Feedback\n"
# Write the attempts data, ensure that the header from the attempts list is not written again
for attempt in attempts[1:]: # Skip the first element which is the header
await asyncio.sleep(0.25) # This delay may not be necessary; adjust as needed
yield attempt
# App installation
app = App(app_ui, server)