Spaces:
Sleeping
Sleeping
| # app.py | |
| import math | |
| from typing import Dict, Any | |
| import gradio as gr | |
| from transformers import AutoTokenizer, pipeline | |
| def darcy_weisbach_head_loss(f: float, L: float, D: float, V: float) -> float: | |
| g = 9.80665 | |
| return f * (L / D) * V**2 / (2 * g) | |
| def reynolds_number(V: float, D: float, nu: float) -> float: | |
| return V * D / nu | |
| def default_friction_factor(Re: float, roughness: float, D: float) -> float: | |
| if Re == 0: | |
| return float('nan') | |
| if Re < 2300: | |
| return 64.0 / Re | |
| return (-1.8 * math.log10((roughness / (3.7 * D))**1.11 + 6.9 / Re))**-2 | |
| def bernoulli_pipe_flow(P1, P2, V1, V2, z1=0.0, z2=0.0, rho=1000.0, mu=0.001, | |
| roughness=1.5e-6, D=0.1, L=1.0, friction_factor=None, | |
| use_darcy=True, g=9.80665) -> Dict[str, Any]: | |
| errors = [] | |
| if rho <= 0: errors.append('rho must be > 0') | |
| if D <= 0: errors.append('D must be > 0') | |
| if L < 0: errors.append('L must be >= 0') | |
| for val in [P1, P2, V1, V2, z1, z2, rho, mu, D, L]: | |
| if not (isinstance(val, (int, float)) and math.isfinite(val)): | |
| errors.append('Inputs must be finite numbers') | |
| break | |
| if errors: | |
| return {'ok': False, 'errors': errors} | |
| nu = mu / rho | |
| Re1 = reynolds_number(V1, D, nu) | |
| Re2 = reynolds_number(V2, D, nu) | |
| f = friction_factor if friction_factor is not None else default_friction_factor(max(Re1, Re2), roughness, D) | |
| h_f = darcy_weisbach_head_loss(f, L, D, (V1 + V2) / 2.0) if use_darcy else 0.0 | |
| left = P1 / (rho * g) + V1**2 / (2 * g) + z1 | |
| right = P2 / (rho * g) + V2**2 / (2 * g) + z2 | |
| h_pump = left - right + h_f | |
| results = { | |
| 'ok': True, | |
| 'inputs': {'P1': P1, 'P2': P2, 'V1': V1, 'V2': V2, 'z1': z1, 'z2': z2, | |
| 'rho': rho, 'mu': mu, 'D': D, 'L': L, 'roughness': roughness}, | |
| 'intermediate': {'g': g, 'nu': nu, 'Re1': Re1, 'Re2': Re2, | |
| 'friction_factor': f, 'head_loss': h_f}, | |
| 'computed': {'lhs': left, 'rhs': right, 'pump_head': h_pump}, | |
| 'summary': f"Pump head required = {h_pump:.3f} m, f = {f:.4f}, h_f = {h_f:.3f} m.", | |
| 'explanations': [f"ν = {nu:.3e}, Re1={Re1:.1f}, Re2={Re2:.1f}", | |
| f"f = {f:.4f}", f"h_f = {h_f:.3f} m", | |
| f"Left={left:.3f}, Right={right:.3f}, h_pump={h_pump:.3f}"] | |
| } | |
| return results | |
| def deterministic_explainer(record: dict) -> str: | |
| if not record.get('ok', False): | |
| return 'Errors: ' + '; '.join(record.get('errors', ['Unknown error'])) | |
| lines = [] | |
| lines.append('--- EXPLANATION (deterministic) ---') | |
| lines.append(str(record.get('summary', ''))) | |
| lines.append('\nInputs:') | |
| for k,v in record['inputs'].items(): | |
| lines.append(f" - {k}: {v}") | |
| lines.append('\nIntermediate:') | |
| for k,v in record['intermediate'].items(): | |
| lines.append(f" - {k}: {v}") | |
| lines.append('\nSteps:') | |
| for s in record.get('explanations', []): | |
| lines.append(' * ' + s) | |
| return '\n'.join(lines) | |
| MODEL_ID = "TinyLlama/TinyLlama-1.1B-Chat-v1.0" | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) | |
| pipe = pipeline("text-generation", model=MODEL_ID, tokenizer=tokenizer) | |
| def _fmt_num(x, sig=4): | |
| """Safe, short formatting for numbers (returns string).""" | |
| try: | |
| if x is None: | |
| return "N/A" | |
| if isinstance(x, (int,)): | |
| return str(x) | |
| if isinstance(x, float): | |
| # use general format with sig significant digits | |
| return f"{x:.{sig}g}" | |
| return str(x) | |
| except Exception: | |
| return str(x) | |
| def _llm_generate(prompt: str, max_tokens: int = 300) -> str: | |
| """ | |
| Run the local pipeline, then strip any echoed prompt and common instruction text. | |
| If the model echoes instructions, do one gentle retry with a simplified prompt. | |
| """ | |
| # Primary generation: deterministic (no sampling) is usually safer for engineering text. | |
| try: | |
| out = pipe( | |
| prompt, | |
| max_new_tokens=max_tokens, | |
| do_sample=False, | |
| temperature=0.0, | |
| return_full_text=True, | |
| ) | |
| except Exception: | |
| # fallback: try return_full_text=False if first attempt fails for this model | |
| out = pipe( | |
| prompt, | |
| max_new_tokens=max_tokens, | |
| do_sample=False, | |
| temperature=0.0, | |
| return_full_text=False, | |
| ) | |
| # get text (handle both pipeline variants) | |
| text = "" | |
| if isinstance(out, list) and len(out) > 0: | |
| text = out[0].get("generated_text", "") or out[0].get("text", "") or "" | |
| text = text or "" | |
| # If the model returned the prompt + output, strip the prompt if present | |
| if text.startswith(prompt): | |
| text = text[len(prompt):] | |
| text = text.strip() | |
| # If output looks like it merely repeated instructions, try a simpler short-prompt retry | |
| low_quality_indicators = [ | |
| "Use bullet points", "Be sure to include", "Do not", "Do NOT", | |
| "Now produce", "System:", "User:", "Instruction:" | |
| ] | |
| if (not text) or any(ind in text for ind in low_quality_indicators) or len(text) < 10: | |
| # simple short retry prompt asking for only the final answer | |
| simple_prompt = prompt + "\n\nNow produce ONLY the requested explanation below (no re-statement of the prompt or instructions):\n" | |
| try: | |
| out2 = pipe( | |
| simple_prompt, | |
| max_new_tokens=max_tokens, | |
| do_sample=False, | |
| temperature=0.0, | |
| return_full_text=True, | |
| ) | |
| except Exception: | |
| out2 = pipe( | |
| simple_prompt, | |
| max_new_tokens=max_tokens, | |
| do_sample=False, | |
| temperature=0.0, | |
| return_full_text=False, | |
| ) | |
| text2 = out2[0].get("generated_text", "") or out2[0].get("text", "") or "" | |
| if text2.startswith(simple_prompt): | |
| text2 = text2[len(simple_prompt):] | |
| text2 = text2.strip() | |
| if text2 and len(text2) > 10 and not any(ind in text2 for ind in low_quality_indicators): | |
| return text2 | |
| # final fallback: | |
| return "[LLM failed to generate a usable explanation — try a different model or reduce the prompt size]" | |
| return text | |
| def llm_explain(record: dict) -> str: | |
| if not record.get("ok", False): | |
| return "Errors: " + "; ".join(record.get("errors", [])) | |
| prompt = ( | |
| "Summarize these Bernoulli pipe flow results clearly and factually, " | |
| "without inventing extra details.\n" | |
| "Mention the proximity of the pump and frictional head loss to eachother and if net head gain is present. \n" | |
| f"Summary: {record.get('summary','')}\n" | |
| f"Intermediate: {record.get('intermediate',{})}\n" | |
| f"Computed: {record.get('computed',{})}\n" | |
| "Explanation:" | |
| ) | |
| output = pipe(prompt, max_new_tokens=150, do_sample=False)[0]["generated_text"] | |
| return output[len(prompt):].strip() | |
| def compute_and_explain(P1,P2,V1,V2,z1,z2,rho,mu,D,L,roughness,use_darcy,mode): | |
| record = bernoulli_pipe_flow(P1,P2,V1,V2,z1,z2,rho,mu,roughness,D,L,None,use_darcy) | |
| if not record.get('ok'): | |
| return record, 'Errors: ' + '; '.join(record.get('errors', [])) | |
| if mode == 'deterministic': | |
| explanation = deterministic_explainer(record) | |
| elif mode == 'local_llm': | |
| explanation = llm_explain(record) | |
| else: | |
| explanation = "Unknown explanation mode." | |
| return record, explanation | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Bernoulli Pipe Flow Calculator") | |
| gr.Markdown("This Space hosts a Bernoulli pipe flow calculator for calculating the pump head loss for an internal flow system through a pipe. An example of one such pipe system is shown below. To utilize this calculator, simply fill in the required system metrics and hit 'compute'.") | |
| gr.Image("bernoulli.png", label="Bernoulli Diagram", show_label=True) | |
| gr.Markdown("NOTE: Roughness (ε) in pump head loss calculations, measured in meters, represents the average height of internal surface irregularities (like ribs or grooves) in a pipe, which contributes to friction and thus head loss. This absolute roughness is used with the pipe's diameter (D) to calculate relative roughness (ε/D), a key factor, along with the Reynolds number, in determining the Darcy friction factor (f, an output of this calculator) needed for the Darcy-Weisbach equation to find major frictional head losses in a pipe system. ") | |
| gr.Markdown("\n To simplify calculations, this calculator also requires a value for 'mu', or the dynamic viscosity of the fluid (Pa*s)") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| P1 = gr.Number(value=101325, label='P1 [Pa]') | |
| P2 = gr.Number(value=101325, label='P2 [Pa]') | |
| V1 = gr.Number(value=1.0, label='V1 [m/s]') | |
| V2 = gr.Number(value=1.0, label='V2 [m/s]') | |
| z1 = gr.Number(value=0.0, label='z1 [m]') | |
| z2 = gr.Number(value=0.0, label='z2 [m]') | |
| rho = gr.Number(value=1000.0, label='rho [kg/m^3]') | |
| mu = gr.Number(value=0.001, label='mu [Pa.s]') | |
| D = gr.Number(value=0.1, label='D [m]') | |
| L = gr.Number(value=10.0, label='L [m]') | |
| roughness = gr.Number(value=1.5e-6, label='roughness [m]') | |
| use_darcy = gr.Checkbox(value=True, label='Use Darcy–Weisbach') | |
| run_btn = gr.Button('Compute') | |
| with gr.Column(scale=3): | |
| numeric_out = gr.JSON(label='Numeric result (JSON)') | |
| explain_mode = gr.Radio(['deterministic', 'local_llm'], value='deterministic', label='Explanation mode') | |
| explanation_out = gr.Textbox(lines=15, label='Explanation') | |
| run_btn.click(compute_and_explain, | |
| inputs=[P1,P2,V1,V2,z1,z2,rho,mu,D,L,roughness,use_darcy,explain_mode], | |
| outputs=[numeric_out, explanation_out]) | |
| if __name__ == "__main__": | |
| demo.queue().launch() | |