Upload app.py with huggingface_hub
Browse files
app.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math, json
|
| 2 |
+
import gradio as gr
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from typing import Dict, Any
|
| 5 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
|
| 6 |
+
|
| 7 |
+
LLM_ID = "HuggingFaceTB/SmolLM2-135M-Instruct"
|
| 8 |
+
tokenizer = AutoTokenizer.from_pretrained(LLM_ID)
|
| 9 |
+
llm = pipeline(
|
| 10 |
+
task="text-generation",
|
| 11 |
+
model=AutoModelForCausalLM.from_pretrained(LLM_ID),
|
| 12 |
+
tokenizer=tokenizer,
|
| 13 |
+
device_map="auto",
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
def projectile_calc(v0_mps: float, theta_deg: float, y0_m: float, g: float, weight_kg: float) -> Dict[str, Any]:
|
| 17 |
+
errors = []
|
| 18 |
+
if not (0 < v0_mps <= 500): errors.append("Initial speed must be in (0, 500] m/s.")
|
| 19 |
+
if not (-10 <= theta_deg <= 90): errors.append("Launch angle must be between -10° and 90°.")
|
| 20 |
+
if not (0 <= y0_m <= 1000): errors.append("Initial height must be in [0, 1000] m.")
|
| 21 |
+
if not (1 <= g <= 50): errors.append("Gravity must be in [1, 50] m/s².")
|
| 22 |
+
if not (0.05 <= weight_kg <= 50): errors.append("Ball weight must be in [0.05, 50] kg.")
|
| 23 |
+
if errors:
|
| 24 |
+
return {"ok": False, "errors": errors}
|
| 25 |
+
|
| 26 |
+
theta_rad = math.radians(theta_deg)
|
| 27 |
+
v0x = v0_mps * math.cos(theta_rad)
|
| 28 |
+
v0y = v0_mps * math.sin(theta_rad)
|
| 29 |
+
|
| 30 |
+
disc = v0y**2 + 2.0 * g * y0_m
|
| 31 |
+
t_flight = (v0y + math.sqrt(max(disc, 0.0))) / g
|
| 32 |
+
t_flight = max(t_flight, 0.0)
|
| 33 |
+
|
| 34 |
+
range_m = v0x * t_flight
|
| 35 |
+
t_apex = max(v0y / g, 0.0)
|
| 36 |
+
y_apex = y0_m + v0y * t_apex - 0.5 * g * t_apex**2
|
| 37 |
+
vy_final = -math.sqrt(max(v0y**2 + 2.0 * g * y0_m, 0.0))
|
| 38 |
+
v_impact = math.sqrt(v0x**2 + vy_final**2)
|
| 39 |
+
|
| 40 |
+
return {
|
| 41 |
+
"ok": True,
|
| 42 |
+
"inputs": {
|
| 43 |
+
"v0_mps": v0_mps,
|
| 44 |
+
"theta_deg": theta_deg,
|
| 45 |
+
"y0_m": y0_m,
|
| 46 |
+
"g_mps2": g,
|
| 47 |
+
"weight_kg": weight_kg,
|
| 48 |
+
},
|
| 49 |
+
"derived": {
|
| 50 |
+
"t_apex_s": t_apex,
|
| 51 |
+
},
|
| 52 |
+
"outputs": {
|
| 53 |
+
"time_of_flight_s": t_flight,
|
| 54 |
+
"range_m": range_m,
|
| 55 |
+
"max_height_m": y_apex,
|
| 56 |
+
"impact_speed_mps": v_impact,
|
| 57 |
+
},
|
| 58 |
+
"notes": {
|
| 59 |
+
"assumptions": ["No air resistance.", "Flat landing at y=0.", "Constant gravity g."],
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
SYSTEM_PROMPT = (
|
| 64 |
+
"You write a single concise sentence using ONLY numbers provided in the JSON. "
|
| 65 |
+
"Do not invent numbers or facts. Use units exactly as given; round to 2–3 sig figs if needed."
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
def llm_one_sentence(structured: Dict[str, Any]) -> str:
|
| 69 |
+
i = structured["inputs"]
|
| 70 |
+
d = structured["derived"]
|
| 71 |
+
o = structured["outputs"]
|
| 72 |
+
payload = {
|
| 73 |
+
"inputs": {
|
| 74 |
+
"v0_mps": round(i["v0_mps"], 3),
|
| 75 |
+
"theta_deg": round(i["theta_deg"], 3),
|
| 76 |
+
"y0_m": round(i["y0_m"], 3),
|
| 77 |
+
"g_mps2": round(i["g_mps2"], 3),
|
| 78 |
+
"weight_kg": round(i["weight_kg"], 3),
|
| 79 |
+
},
|
| 80 |
+
"derived": {
|
| 81 |
+
"t_apex_s": round(d["t_apex_s"], 4),
|
| 82 |
+
},
|
| 83 |
+
"outputs": {
|
| 84 |
+
"time_of_flight_s": round(o["time_of_flight_s"], 4),
|
| 85 |
+
"range_m": round(o["range_m"], 3),
|
| 86 |
+
"max_height_m": round(o["max_height_m"], 3),
|
| 87 |
+
"impact_speed_mps": round(o["impact_speed_mps"], 3),
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
instruction = (
|
| 91 |
+
"Produce EXACTLY one sentence of this form using only the JSON values:\n"
|
| 92 |
+
""If you threw a ball that weighed {weight_kg} kg at v0={v0_mps} m/s, "
|
| 93 |
+
"θ={theta_deg}°, from y0={y0_m} m under g={g_mps2} m/s², "
|
| 94 |
+
"it would stay in the air for {time_of_flight_s} s, reach {max_height_m} m, "
|
| 95 |
+
"travel {range_m} m, and impact at {impact_speed_mps} m/s.""
|
| 96 |
+
)
|
| 97 |
+
user_prompt = f"JSON:\n{json.dumps(payload, indent=2)}\n\nFollow the instruction exactly."
|
| 98 |
+
prompt = f"<|system|>\n{SYSTEM_PROMPT}\n<|user|>\n{instruction}\n\n{user_prompt}\n<|assistant|>"
|
| 99 |
+
out = llm(prompt, max_new_tokens=120, do_sample=False, temperature=0.0, return_full_text=False)
|
| 100 |
+
return out[0]["generated_text"].strip()
|
| 101 |
+
|
| 102 |
+
def run_once(v0_mps, theta_deg, y0_m, g_mps2, weight_kg):
|
| 103 |
+
rec = projectile_calc(float(v0_mps), float(theta_deg), float(y0_m), float(g_mps2), float(weight_kg))
|
| 104 |
+
if not rec.get("ok", False):
|
| 105 |
+
errs = rec.get("errors", ["Invalid input."])
|
| 106 |
+
df = pd.DataFrame([{"Error": "; ".join(errs)}])
|
| 107 |
+
return df, "Please adjust inputs."
|
| 108 |
+
i, d, o = rec["inputs"], rec["derived"], rec["outputs"]
|
| 109 |
+
df = pd.DataFrame([{
|
| 110 |
+
"v0_mps [m/s]": round(i["v0_mps"], 3),
|
| 111 |
+
"theta_deg [deg]": round(i["theta_deg"], 3),
|
| 112 |
+
"y0_m [m]": round(i["y0_m"], 3),
|
| 113 |
+
"g_mps2 [m/s²]": round(i["g_mps2"], 3),
|
| 114 |
+
"weight_kg [kg]": round(i["weight_kg"], 3),
|
| 115 |
+
"t_apex_s [s]": round(d["t_apex_s"], 4),
|
| 116 |
+
"max_height_m [m]": round(o["max_height_m"], 3),
|
| 117 |
+
"time_of_flight_s [s]": round(o["time_of_flight_s"], 4),
|
| 118 |
+
"range_m [m]": round(o["range_m"], 3),
|
| 119 |
+
"impact_speed_mps [m/s]": round(o["impact_speed_mps"], 3),
|
| 120 |
+
}])
|
| 121 |
+
sentence = llm_one_sentence(rec)
|
| 122 |
+
return df, sentence
|
| 123 |
+
|
| 124 |
+
with gr.Blocks(title="Projectile Motion — Deterministic + One-Sentence LLM") as demo:
|
| 125 |
+
gr.Markdown("# Projectile Motion — Deterministic Calculator + One-Sentence Explanation")
|
| 126 |
+
gr.Markdown("Deterministic projectile motion (no air resistance). See all inputs and derived values, plus a single, grounded sentence.")
|
| 127 |
+
|
| 128 |
+
with gr.Row():
|
| 129 |
+
v0 = gr.Slider(1, 200, value=30.0, step=0.5, label="Initial speed v₀ [m/s]")
|
| 130 |
+
theta = gr.Slider(-10, 90, value=45.0, step=0.5, label="Launch angle θ [deg]")
|
| 131 |
+
y0 = gr.Slider(0, 20, value=1.5, step=0.1, label="Initial height y₀ [m]")
|
| 132 |
+
g = gr.Slider(5, 20, value=9.81, step=0.01, label="Gravity g [m/s²]")
|
| 133 |
+
w = gr.Slider(0.05, 10.0, value=0.43, step=0.01, label="Ball weight [kg]")
|
| 134 |
+
|
| 135 |
+
run_btn = gr.Button("Compute")
|
| 136 |
+
|
| 137 |
+
results_df = gr.Dataframe(
|
| 138 |
+
headers=[
|
| 139 |
+
"v0_mps [m/s]", "theta_deg [deg]", "y0_m [m]", "g_mps2 [m/s²]", "weight_kg [kg]",
|
| 140 |
+
"t_apex_s [s]", "max_height_m [m]", "time_of_flight_s [s]", "range_m [m]", "impact_speed_mps [m/s]"
|
| 141 |
+
],
|
| 142 |
+
label="All values (inputs + derived)",
|
| 143 |
+
interactive=False
|
| 144 |
+
)
|
| 145 |
+
explain_md = gr.Markdown(label="One-sentence explanation")
|
| 146 |
+
|
| 147 |
+
run_btn.click(run_once, inputs=[v0, theta, y0, g, w], outputs=[results_df, explain_md])
|
| 148 |
+
|
| 149 |
+
gr.Examples(
|
| 150 |
+
examples=[
|
| 151 |
+
[30.0, 45.0, 1.5, 9.81, 0.43],
|
| 152 |
+
[20.0, 30.0, 0.0, 9.81, 0.145],
|
| 153 |
+
[40.0, 60.0, 0.0, 9.81, 0.45],
|
| 154 |
+
],
|
| 155 |
+
inputs=[v0, theta, y0, g, w],
|
| 156 |
+
label="Representative cases",
|
| 157 |
+
examples_per_page=3,
|
| 158 |
+
cache_examples=False,
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
if __name__ == "__main__":
|
| 162 |
+
demo.launch()
|