kevinkyi's picture
Upload app.py with huggingface_hub
e41099f verified
raw
history blame
7.92 kB
import math, json
import gradio as gr
import pandas as pd
from typing import Dict, Any
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from textwrap import dedent
LLM_ID = "HuggingFaceTB/SmolLM2-135M-Instruct"
tokenizer = AutoTokenizer.from_pretrained(LLM_ID)
llm = pipeline(
task="text-generation",
model=AutoModelForCausalLM.from_pretrained(LLM_ID),
tokenizer=tokenizer,
device_map="auto",
)
def projectile_calc(v0_mps: float, theta_deg: float, y0_m: float, g: float, weight_kg: float) -> Dict[str, Any]:
errors = []
if not (0 < v0_mps <= 500): errors.append("Initial speed must be in (0, 500] m/s.")
if not (-10 <= theta_deg <= 90): errors.append("Launch angle must be between -10° and 90°.")
if not (0 <= y0_m <= 1000): errors.append("Initial height must be in [0, 1000] m.")
if not (1 <= g <= 50): errors.append("Gravity must be in [1, 50] m/s^2.")
if not (0.05 <= weight_kg <= 50): errors.append("Ball weight must be in [0.05, 50] kg.")
if errors:
return {"ok": False, "errors": errors}
theta_rad = math.radians(theta_deg)
v0x = v0_mps * math.cos(theta_rad)
v0y = v0_mps * math.sin(theta_rad)
# Time of flight (positive root)
disc = v0y**2 + 2.0 * g * y0_m
t_flight = (v0y + math.sqrt(max(disc, 0.0))) / g
t_flight = max(t_flight, 0.0)
# Range, apex height, impact speed
range_m = v0x * t_flight
t_apex = max(v0y / g, 0.0)
y_apex = y0_m + v0y * t_apex - 0.5 * g * t_apex**2
vy_final = -math.sqrt(max(v0y**2 + 2.0 * g * y0_m, 0.0))
v_impact = math.sqrt(v0x**2 + vy_final**2)
return {
"ok": True,
"inputs": {
"v0_mps": v0_mps,
"theta_deg": theta_deg,
"y0_m": y0_m,
"g_mps2": g,
"weight_kg": weight_kg,
},
"derived": {
"t_apex_s": t_apex,
},
"outputs": {
"time_of_flight_s": t_flight,
"range_m": range_m,
"max_height_m": y_apex,
"impact_speed_mps": v_impact,
},
"notes": {
"assumptions": ["No air resistance.", "Flat landing at y=0.", "Constant gravity g."],
}
}
SYSTEM_PROMPT = (
"You write a single concise sentence using ONLY numbers provided in the JSON. "
"Do not invent numbers or facts. Use units exactly as given; round to 2–3 sig figs if needed."
)
def llm_one_sentence(structured: Dict[str, Any]) -> str:
i = structured["inputs"]
d = structured["derived"]
o = structured["outputs"]
payload = {
"inputs": {
"v0_mps": round(i["v0_mps"], 3),
"theta_deg": round(i["theta_deg"], 3),
"y0_m": round(i["y0_m"], 3),
"g_mps2": round(i["g_mps2"], 3),
"weight_kg": round(i["weight_kg"], 3),
},
"derived": {
"t_apex_s": round(d["t_apex_s"], 4),
},
"outputs": {
"time_of_flight_s": round(o["time_of_flight_s"], 4),
"range_m": round(o["range_m"], 3),
"max_height_m": round(o["max_height_m"], 3),
"impact_speed_mps": round(o["impact_speed_mps"], 3),
}
}
system_msg = (
"You are a careful technical writer. "
"Write EXACTLY ONE sentence using ONLY numbers provided in the JSON. "
"Do not invent or rename fields; keep units as given; use ~2–3 sig figs."
)
# Show the exact sentence shape we want.
instruction = '''Use only these keys: inputs, derived, outputs.
Return exactly one sentence in this form (fill in the braces with the JSON values):
If you threw a ball that weighed {weight_kg} kg at v0={v0_mps} m/s, θ={theta_deg}, from y0={y0_m} m under g={g_mps2} m/s^2,
it would stay in the air for {time_of_flight_s} s, reach {max_height_m} m, travel {range_m} m, and impact at {impact_speed_mps} m/s.
'''
user_msg = f"JSON:
{json.dumps(payload, indent=2)}
Produce the single sentence as specified."
# ✅ Use the tokenizer's chat template
messages = [
{"role": "system", "content": system_msg},
{"role": "user", "content": instruction},
{"role": "user", "content": user_msg},
]
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
out = llm(
prompt,
max_new_tokens=96,
do_sample=False,
temperature=0.0,
return_full_text=False,
)
text = out[0]["generated_text"].strip()
# Minimal fallback: if the model didn't produce a sensible one-liner, synthesize it.
if ("If you threw a ball" not in text) or (len(text.split()) < 6):
p = payload
text = (
f"If you threw a ball that weighed {p['inputs']['weight_kg']} kg at v0={p['inputs']['v0_mps']} m/s, "
f"θ={p['inputs']['theta_deg']}, from y0={p['inputs']['y0_m']} m under g={p['inputs']['g_mps2']} m/s^2, "
f"it would stay in the air for {p['outputs']['time_of_flight_s']} s, "
f"reach {p['outputs']['max_height_m']} m, travel {p['outputs']['range_m']} m, "
f"and impact at {p['outputs']['impact_speed_mps']} m/s."
)
return text
def run_once(v0_mps, theta_deg, y0_m, g_mps2, weight_kg):
rec = projectile_calc(float(v0_mps), float(theta_deg), float(y0_m), float(g_mps2), float(weight_kg))
if not rec.get("ok", False):
errs = rec.get("errors", ["Invalid input."])
df = pd.DataFrame([{"Error": "; ".join(errs)}])
return df, "Please adjust inputs."
i, d, o = rec["inputs"], rec["derived"], rec["outputs"]
df = pd.DataFrame([{
"v0_mps [m/s]": round(i["v0_mps"], 3),
"theta_deg [deg]": round(i["theta_deg"], 3),
"y0_m [m]": round(i["y0_m"], 3),
"g_mps2 [m/s^2]": round(i["g_mps2"], 3),
"weight_kg [kg]": round(i["weight_kg"], 3),
"t_apex_s [s]": round(d["t_apex_s"], 4),
"max_height_m [m]": round(o["max_height_m"], 3),
"time_of_flight_s [s]": round(o["time_of_flight_s"], 4),
"range_m [m]": round(o["range_m"], 3),
"impact_speed_mps [m/s]": round(o["impact_speed_mps"], 3),
}])
sentence = llm_one_sentence(rec)
return df, sentence
with gr.Blocks(title="Projectile Motion — Deterministic + One-Sentence LLM") as demo:
gr.Markdown("# Projectile Motion — Deterministic Calculator + One-Sentence Explanation")
gr.Markdown("Deterministic projectile motion (no air resistance). See all inputs and derived values, plus a single, grounded sentence.")
with gr.Row():
v0 = gr.Slider(1, 200, value=30.0, step=0.5, label="Initial speed v₀ [m/s]")
theta = gr.Slider(-10, 90, value=45.0, step=0.5, label="Launch angle θ [deg]")
y0 = gr.Slider(0, 20, value=1.5, step=0.1, label="Initial height y₀ [m]")
g = gr.Slider(5, 20, value=9.81, step=0.01, label="Gravity g [m/s^2]")
w = gr.Slider(0.05, 10.0, value=0.43, step=0.01, label="Ball weight [kg]")
run_btn = gr.Button("Compute")
results_df = gr.Dataframe(
headers=[
"v0_mps [m/s]", "theta_deg [deg]", "y0_m [m]", "g_mps2 [m/s^2]", "weight_kg [kg]",
"t_apex_s [s]", "max_height_m [m]", "time_of_flight_s [s]", "range_m [m]", "impact_speed_mps [m/s]"
],
label="All values (inputs + derived)",
interactive=False
)
explain_md = gr.Markdown(label="One-sentence explanation")
run_btn.click(run_once, inputs=[v0, theta, y0, g, w], outputs=[results_df, explain_md])
gr.Examples(
examples=[
[30.0, 45.0, 1.5, 9.81, 0.43],
[20.0, 30.0, 0.0, 9.81, 0.145],
[40.0, 60.0, 0.0, 9.81, 0.45],
],
inputs=[v0, theta, y0, g, w],
label="Representative cases",
examples_per_page=3,
cache_examples=False,
)
if __name__ == "__main__":
demo.launch()