# --- deps (minimal) --- # !pip -q install "gradio>=4.44.0" "transformers>=4.42.0" # --- deterministic backend --- from math import pi import re import gradio as gr from transformers import pipeline def validate_and_compute( driver_teeth:int, driven_teeth:int, input_torque_Nm:float, input_speed_rpm:float, efficiency_pct:float, ): """ Deterministic, first-principles 2-gear calculator. SCOPE & ASSUMPTIONS - Two rigid spur gears (driver -> driven), single mesh, no slippage. - Quasi-steady operation; efficiency is mesh efficiency only. - No shaft/bearing losses; no strength checks. INPUTS & VALID RANGES - driver_teeth: 6..400 (int) - driven_teeth: 6..400 (int) - input_torque_Nm: 0..10_000 [N·m] - input_speed_rpm: 0..30_000 [rpm] - efficiency_pct: 50..100 [%] """ problems = [] if not isinstance(driver_teeth, int) or not (6 <= driver_teeth <= 400): problems.append("driver_teeth must be an integer in [6, 400].") if not isinstance(driven_teeth, int) or not (6 <= driven_teeth <= 400): problems.append("driven_teeth must be an integer in [6, 400].") if not (0 <= input_torque_Nm <= 10_000): problems.append("input_torque_Nm must be in [0, 10_000] N·m.") if not (0 <= input_speed_rpm <= 30_000): problems.append("input_speed_rpm must be in [0, 30_000] rpm.") if not (50 <= efficiency_pct <= 100): problems.append("efficiency_pct must be in [50, 100] %.") if problems: return {"ok": False, "errors": problems} gear_ratio = driven_teeth / driver_teeth # >1 → torque↑ speed↓ eta = efficiency_pct / 100.0 # kinematics output_speed_rpm = input_speed_rpm / gear_ratio # torque (power*eta conserved across mesh) output_torque_Nm = input_torque_Nm * gear_ratio * eta # power check omega_in = input_speed_rpm * (2*pi/60.0) # rad/s omega_out = output_speed_rpm * (2*pi/60.0) # rad/s pin_W = input_torque_Nm * omega_in pout_W = output_torque_Nm * omega_out expected_pout_W = pin_W * eta power_balance_error_pct = (0 if expected_pout_W == 0 else 100.0 * (pout_W - expected_pout_W) / max(1e-12, expected_pout_W)) speed_change = "decrease" if gear_ratio > 1 else ("increase" if gear_ratio < 1 else "no change") torque_change = "increase" if gear_ratio > 1 else ("decrease" if gear_ratio < 1 else "no change") return { "ok": True, "inputs": { "driver_teeth": driver_teeth, "driven_teeth": driven_teeth, "input_torque_Nm": float(input_torque_Nm), "input_speed_rpm": float(input_speed_rpm), "efficiency_pct": float(efficiency_pct) }, "intermediate": { "gear_ratio": float(gear_ratio), "eta": float(eta) }, "outputs": { "output_speed_rpm": float(output_speed_rpm), "output_torque_Nm": float(output_torque_Nm), "input_power_W": float(pin_W), "output_power_W": float(pout_W), "expected_output_power_W": float(expected_pout_W), "power_balance_error_percent": float(power_balance_error_pct), "qualitative": { "speed_change": speed_change, "torque_change": torque_change } } } # --- compact, deterministic LLM explainer --- explainer = pipeline("text2text-generation", model="google/flan-t5-small") def sanitize_to_plain_sentences(text: str, max_sentences: int = 6) -> str: """Drop code/JSON-y lines and keep up to N plain sentences.""" # Strip code blocks/backticks text = re.sub(r"```.*?```", "", text, flags=re.S) text = text.replace("`", "") # Keep only part after the last 'Explanation:' if present if "Explanation:" in text: text = text.split("Explanation:")[-1] # Remove obvious code-ish lines cleaned_lines = [] for line in text.splitlines(): l = line.strip() if not l: continue if re.search(r"\binput\(", l) or re.search(r"\bprint\(", l): continue if re.match(r"^\s*[A-Za-z_]\w*\s*=", l): # assignments continue if l.startswith("{") or l.startswith("[") or l.endswith("}"): continue cleaned_lines.append(l) text = " ".join(cleaned_lines) # Split into sentences conservatively # (avoid splitting inside units like N·m, rpm—these don’t have periods) sentences = re.split(r"(?<=[.!?])\s+", text) # Trim and keep sentences that look like natural language plain = [] for s in sentences: s = s.strip() if not s: continue # Heuristics to avoid code/fragments if re.search(r"[{};]|==|!=|<=|>=|\breturn\b|\bdef\b|\bclass\b", s): continue plain.append(s) if len(plain) >= max_sentences: break return " ".join(plain).strip() def fallback_explanation(payload: dict) -> str: """Deterministic template if the LLM output gets sanitized to nothing/too short.""" inp = payload["inputs"]; mid = payload["intermediate"]; out = payload["outputs"] sc = out["qualitative"]["speed_change"]; tc = out["qualitative"]["torque_change"] return ( f"The driven gear has {inp['driven_teeth']} teeth and the driver has {inp['driver_teeth']}, " f"so the gear ratio is {mid['gear_ratio']:.3f} (driven/driver). " f"With an input torque of {inp['input_torque_Nm']:.3g} N·m at {inp['input_speed_rpm']:.3g} rpm, " f"the output speed becomes {out['output_speed_rpm']:.3g} rpm (a {sc} in speed) and the output torque is " f"{out['output_torque_Nm']:.3g} N·m (a {tc} in torque). " f"Input power is {out['input_power_W']:.3g} W; with an efficiency of {inp['efficiency_pct']:.3g}%, " f"the expected output power is {out['expected_output_power_W']:.3g} W, which matches the computed " f"{out['output_power_W']:.3g} W. " f"This calculation considers mesh efficiency only; check tooth strength, shafts, and bearings separately." ) def render_explanation(payload: dict) -> str: if not payload.get("ok"): return "There were validation errors; please fix inputs and try again." inp = payload["inputs"]; mid = payload["intermediate"]; out = payload["outputs"] # Only pass essentials so the model can’t riff on raw JSON. facts = ( f"Driver teeth: {inp['driver_teeth']}; Driven teeth: {inp['driven_teeth']}. " f"Input torque: {inp['input_torque_Nm']:.4g} N·m; Input speed: {inp['input_speed_rpm']:.4g} rpm. " f"Efficiency: {inp['efficiency_pct']:.4g}%. " f"Gear ratio (driven/driver): {mid['gear_ratio']:.6g}. " f"Output speed: {out['output_speed_rpm']:.6g} rpm; Output torque: {out['output_torque_Nm']:.6g} N·m. " f"Input power: {out['input_power_W']:.6g} W; Output power: {out['output_power_W']:.6g} W; " f"Expected Pin×η: {out['expected_output_power_W']:.6g} W." ) prompt = ( "You are a careful engineering assistant. Using only the facts below, write 4–6 clear sentences that " "explain the gear calculation to a non-expert. Mention the gear ratio’s tradeoff (speed vs torque) and " "comment that output power is approximately input power times efficiency. " "Do NOT include code, equations, lists, JSON, or symbols like '=', '{', '}'. Plain sentences only.\n\n" f"{facts}\n\nExplanation:" ) gen = explainer( prompt, max_new_tokens=140, do_sample=False, # deterministic num_beams=4, early_stopping=True )[0]["generated_text"] cleaned = sanitize_to_plain_sentences(gen) # If the model still produced garbage, fall back to a deterministic template. if len(cleaned.split()) < 25: # too short / likely sanitized away cleaned = fallback_explanation(payload) return cleaned # --- Gradio app (no JSON panel; with clickable examples) --- def app_fn(driver_teeth, driven_teeth, input_torque, input_speed, efficiency): result = validate_and_compute( int(driver_teeth), int(driven_teeth), float(input_torque), float(input_speed), float(efficiency), ) if not result["ok"]: errors_md = "### Validation Errors\n" + "\n".join([f"- {e}" for e in result["errors"]]) return errors_md, "—" r = result def fmt(x, digits=6): # compact pretty printer return f"{x:.{digits}f}".rstrip('0').rstrip('.') if isinstance(x, float) else str(x) md = f""" ### Numerical Results - **Gear ratio (driven/driver):** {fmt(r['intermediate']['gear_ratio'], 6)} - **Output speed:** {fmt(r['outputs']['output_speed_rpm'], 6)} rpm _(speed {r['outputs']['qualitative']['speed_change']})_ - **Output torque:** {fmt(r['outputs']['output_torque_Nm'], 6)} N·m _(torque {r['outputs']['qualitative']['torque_change']})_ - **Input power:** {fmt(r['outputs']['input_power_W'], 6)} W - **Output power:** {fmt(r['outputs']['output_power_W'], 6)} W - **Expected output power (Pin×η):** {fmt(r['outputs']['expected_output_power_W'], 6)} W - **Power balance error:** {fmt(r['outputs']['power_balance_error_percent'], 6)} % """ explanation = render_explanation(result) return md, explanation with gr.Blocks(title="Deterministic Gear Calculator (See the Math)") as demo: gr.Markdown("# Deterministic Gear Calculator — with Natural-Language Explanation") gr.Markdown( "Two-gear train (driver → driven). Deterministic math with validation and a plain-English explanation." ) with gr.Row(): with gr.Column(): in_driver = gr.Number(label="Driver gear teeth", value=20, precision=0) in_driven = gr.Number(label="Driven gear teeth", value=65, precision=0) in_torque = gr.Number(label="Input torque [N·m]", value=12.0) in_speed = gr.Number(label="Input speed [rpm]", value=1800.0) in_eta = gr.Slider(50, 100, value=97.0, step=0.1, label="Mesh efficiency [%]") run_btn = gr.Button("Calculate", variant="primary") with gr.Column(): results_md = gr.Markdown(label="Numerical Results") explanation_md = gr.Markdown(label="Explain the results") gr.Markdown("### Examples (click to fill)") examples = [ [20, 60, 10.0, 1800.0, 97.0], # 3:1 reduction [18, 36, 5.0, 1500.0, 96.0], # 2:1 reduction [32, 24, 8.0, 1200.0, 95.0], # overdrive [25, 75, 15.0, 3600.0, 98.0], # 3:1 with higher speed ] gr.Examples( examples=examples, inputs=[in_driver, in_driven, in_torque, in_speed, in_eta], label="Common scenarios" ) run_btn.click( app_fn, [in_driver, in_driven, in_torque, in_speed, in_eta], [results_md, explanation_md] ) demo.launch()