cmuchancel's picture
Deploy notebook fin calculator and local LLM explainer
902b9c5 verified
Raw History Blame Contribute Delete
7.93 kB
import math
def fin_array_calc(N: int, T_inf_C: float, T_b_C: float, L_m: float, w_m: float, t_m: float, h_W_m2K: float, k_W_mK: float) -> dict:
"""
Straight rectangular fin array, adiabatic tip.
Returns fin geometry, fin parameter, heat transfer per fin, and total heat transfer.
"""
values = [N, T_inf_C, T_b_C, L_m, w_m, t_m, h_W_m2K, k_W_mK]
if not all(math.isfinite(v) for v in values):
raise ValueError("All inputs must be finite.")
if N <= 0 or min(L_m, w_m, t_m, h_W_m2K, k_W_mK) <= 0:
raise ValueError("Number of fins, dimensions, convection coefficient, and conductivity must be positive.")
if T_b_C <= T_inf_C:
raise ValueError("Base temperature must be greater than ambient temperature.")
if N != int(N):
raise ValueError("Number of fins must be an integer.")
# Calculate fin geometry
A_c = w_m * t_m
P = 2.0 * (w_m + t_m)
# Base excess temperature
theta_b = T_b_C - T_inf_C
# Fin parameter
m = math.sqrt(h_W_m2K * P / (k_W_mK * A_c))
M = math.sqrt(h_W_m2K * P * k_W_mK * A_c) * theta_b
# Heat transfer for adiabatic tip
q_fin_W = M * math.tanh(m * L_m)
q_total_W = N * q_fin_W
return dict(
results={
"A_c_m2": A_c,
"P_m": P,
"theta_b_C": theta_b,
"m_1_per_m": m,
"mL": m * L_m,
"q_fin_W": q_fin_W,
"q_total_W": q_total_W,
},
assumptions={
"adiabatic_tip": True,
"steady_state": True,
"one_dimensional": True,
"constant_h": True,
"constant_T_inf": True,
"constant_T_b": True,
"constant_k": True,
"uniform_cross_section": True,
"identical_fins": True,
"perfect_base_contact": True,
"negligible_radiation": True,
"no_fin_interaction": True,
},
)
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
MODEL_ID = "Qwen/Qwen3-0.6B"
MODEL_REVISION = "c1899de289a04d12100db370d81485cdf75e47ca"
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, revision=MODEL_REVISION)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, revision=MODEL_REVISION, dtype=torch.float32
).eval() # CPU: works without a GPU runtime or a separate serving package.
def generate(messages, max_tokens=96):
inputs = tokenizer.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True,
enable_thinking=False, return_dict=True, return_tensors="pt",
)
config = GenerationConfig(
max_new_tokens=max_tokens, do_sample=False,
eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.eos_token_id,
)
model.generation_config = config # Keep sampling defaults from overriding this classroom configuration.
with torch.inference_mode():
output = model.generate(**inputs, generation_config=config)
# Decode only the new tokens, not the prompt.
new_tokens = output[0, inputs["input_ids"].shape[-1]:]
text = tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
if new_tokens[-1].item() != tokenizer.eos_token_id:
text += "\n[Generation reached its length limit; this explanation may be incomplete.]"
return text
import pandas as pd
import gradio as gr
TABLE_COLUMNS = ["Quantity", "Calculated", "Units"]
# Two examples teach the model how to describe the fin-array calculation.
FEW_SHOT_MESSAGES = [
{"role": "user", "content": "Heat transfer per fin: 8.000 W. Number of fins: 10. Total heat transfer: 80.000 W. Base temperature: 100.000 C. Ambient temperature: 25.000 C."},
{"role": "assistant", "content": "Each fin transfers 8 W from the base to the surroundings. With 10 identical fins, the total heat transfer is 80 W."},
{"role": "user", "content": "Heat transfer per fin: 4.000 W. Number of fins: 20. Total heat transfer: 80.000 W. Base temperature: 80.000 C. Ambient temperature: 30.000 C."},
{"role": "assistant", "content": "Each fin transfers 4 W from the base to the surroundings. With 20 identical fins, the total heat transfer is 80 W."},
]
def run_once(num_fins, ambient, base, length, width, thickness, h, conductivity):
try:
vals = [float(x) for x in [num_fins, ambient, base, length, width, thickness, h, conductivity]]
result = fin_array_calc(int(vals[0]), vals[1], vals[2], vals[3], vals[4], vals[5], vals[6], vals[7])
except (TypeError, ValueError, ArithmeticError) as exc:
# Clear stale evidence and prose together when an input is invalid.
return pd.DataFrame(columns=TABLE_COLUMNS), "Correct the inputs: " + str(exc)
r = result["results"]
table = pd.DataFrame([
["Cross-sectional area", f"{r['A_c_m2']:.6g}", "m²"],
["Fin perimeter", f"{r['P_m']:.6g}", "m"],
["Base excess temperature", f"{r['theta_b_C']:.3f}", "°C"],
["Fin parameter, m", f"{r['m_1_per_m']:.3f}", "1/m"],
["mL", f"{r['mL']:.3f}", "—"],
["Heat transfer per fin", f"{r['q_fin_W']:.3f}", "W"],
["Total array heat transfer", f"{r['q_total_W']:.3f}", "W"],
], columns=TABLE_COLUMNS)
facts = (
f"Heat transfer per fin: {r['q_fin_W']:.3f} W. Number of fins: {int(vals[0])}. Total heat transfer: {r['q_total_W']:.3f} W. "
f"Base temperature: {vals[2]:.3f} C. Ambient temperature: {vals[1]:.3f} C. Fin parameter m: {r['m_1_per_m']:.3f} 1/m. mL: {r['mL']:.3f}."
)
explanation = generate([
{"role": "system", "content": "Write exactly two short sentences, at most 35 words total. Sentence 1: state heat transfer per fin. Sentence 2: state total array heat transfer. Copy the supplied numbers exactly. Do not repeat values, recalculate, invent causes, or recommend design changes."},
*FEW_SHOT_MESSAGES,
{"role": "user", "content": facts},
])
return table, explanation
with gr.Blocks() as demo:
gr.Markdown("# Fin array calculation and explanation\nIdealized rectangular fins with adiabatic tips. Compare the generated description against the calculated values.")
gr.Markdown(
"""
**Assumptions**
- Steady-state, one-dimensional conduction
- Adiabatic fin tips
- Constant `h`, `k`, `T∞`, and `Tb`
- Uniform rectangular cross-section
- Identical fins with perfect base contact
- Negligible radiation and fin-to-fin interaction
"""
)
gr.Markdown(
"""
**Valid inputs**
- Number of fins: positive integer
- Fin length, width, and thickness: greater than 0 m
- Convection coefficient `h`: greater than 0 W/m²-K
- Thermal conductivity `k`: greater than 0 W/m-K
- Base temperature `Tb` must be greater than ambient temperature `T∞`
"""
)
with gr.Row():
num_fins = gr.Number(value=10, precision=0, label="Number of fins")
ambient = gr.Number(value=25, label="Ambient temperature (°C)")
base = gr.Number(value=100, label="Base temperature (°C)")
h = gr.Number(value=25, label="Convection coefficient (W/m²-K)")
with gr.Row():
length = gr.Number(value=0.05, label="Fin length (m)")
width = gr.Number(value=0.03, label="Fin width (m)")
thickness = gr.Number(value=0.002, label="Fin thickness (m)")
conductivity = gr.Number(value=205, label="Thermal conductivity (W/m-K)")
table = gr.Dataframe(headers=TABLE_COLUMNS, datatype="str", label="Calculated evidence — source of the results", interactive=False)
prose = gr.Textbox(label="Generated explanation — verify against the table", lines=4, interactive=False)
gr.Button("Calculate and explain").click(
run_once, [num_fins, ambient, base, length, width, thickness, h, conductivity], [table, prose],
concurrency_limit=1, api_name="run_once",
)
if __name__ == "__main__":
demo.launch()