SebastianAndreu commited on
Commit
6b36e09
·
verified ·
1 Parent(s): d8d47e7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +219 -0
app.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ================================================================
2
+ # HW3: Bolt Torque Calculator with Gradio
3
+ #
4
+ # Author: Sebastian Andreu
5
+ # Course: 24679 - Designing and Deploying AI/ML Systems
6
+ # Dataset/Inputs: User-selected bolt geometry, preload, material, lubrication
7
+ # Task: Deterministic first-principles calculation of bolt torque with natural language explanation,
8
+ # deployed via Hugging Face Space
9
+ #
10
+ # Acknowledgments:
11
+ # - Torque calculation formulas based on standard ISO metric bolt theory
12
+ # - Deployment scaffold and documentation supported with AI assistance (ChatGPT, OpenAI)
13
+ # - Reference: Class-provided notebook "LLMs for explanability.ipynb"
14
+ # ================================================================
15
+
16
+ import math # For trigonometry
17
+ import gradio # For building the interface
18
+ import pandas # For working with tables
19
+
20
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline # For LLMs
21
+
22
+ # Instantiate the model that we'll be calling. This is a tiny one!
23
+ MODEL_ID = "HuggingFaceTB/SmolLM2-135M-Instruct"
24
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
25
+ pipe = pipeline(
26
+ task="text-generation",
27
+ model=AutoModelForCausalLM.from_pretrained(
28
+ MODEL_ID,
29
+ ),
30
+ tokenizer=tokenizer
31
+ )
32
+
33
+ # Lookup table for friction coefficients
34
+ # Format: friction_table[material][lubrication] = coefficient
35
+ friction_table = {
36
+ "Steel": {"Dry": 0.15, "Oil": 0.10, "Grease": 0.08, "Zinc/Anti-seize": 0.05},
37
+ "Aluminum": {"Dry": 0.20, "Oil": 0.15, "Grease": 0.12, "Zinc/Anti-seize": 0.10},
38
+ "Brass": {"Dry": 0.18, "Oil": 0.13, "Grease": 0.10, "Zinc/Anti-seize": 0.08},
39
+ "Titanium": {"Dry": 0.25, "Oil": 0.20, "Grease": 0.18, "Zinc/Anti-seize": 0.15},
40
+ }
41
+
42
+ # Function to calculate bolt torque
43
+ def bolt_calc(
44
+ d_mm: float,
45
+ F_N: float,
46
+ p_mm: float,
47
+ mu_t: float,
48
+ mu_n: float,
49
+ d_head_mm: float = None
50
+ ) -> dict:
51
+ """
52
+ Calculates required torque for a standard ISO metric bolt.
53
+ Returns torque [Nm] and a message.
54
+ """
55
+
56
+ # Thread half-angle (ISO metric)
57
+ beta_rad = math.radians(30)
58
+
59
+ # Lead angle
60
+ phi_rad = math.atan(p_mm / (math.pi * d_mm))
61
+
62
+ # If head diameter not provided, assume 1.5 * bolt diameter
63
+ if d_head_mm is None:
64
+ d_head_mm = 1.5 * d_mm
65
+
66
+ # Friction angle
67
+ rho_rad = math.atan(mu_t / math.cos(beta_rad))
68
+
69
+ # Torque calculation
70
+ T_Nm = F_N * (d_mm / 2 * math.tan(phi_rad + rho_rad) + mu_n * d_head_mm / 2)
71
+
72
+ return dict(
73
+ results={
74
+ "torque_Nm": T_Nm,
75
+ },
76
+ verdict={
77
+ "strength_message": "Torque calculated successfully",
78
+ },
79
+ )
80
+
81
+ # Helper for chat formatting
82
+ def _format_chat(system_prompt: str, user_prompt: str) -> str:
83
+ messages = [
84
+ {"role": "system", "content": system_prompt},
85
+ {"role": "user", "content": user_prompt},
86
+ ]
87
+ template = getattr(tokenizer, "chat_template", None)
88
+ return tokenizer.apply_chat_template(
89
+ messages,
90
+ tokenize=False,
91
+ add_generation_prompt=True
92
+ )
93
+
94
+ # LLM text generation
95
+ def _llm_generate(prompt: str, max_tokens: int) -> str:
96
+ out = pipe(
97
+ prompt,
98
+ max_new_tokens=max_tokens,
99
+ do_sample=True,
100
+ temperature=0.5,
101
+ return_full_text=False,
102
+ )
103
+ return out[0]["generated_text"]
104
+
105
+ # Generate natural language explanation
106
+ def llm_explain(results: dict, inputs: list) -> str:
107
+ d_mm, F_N, p_mm, mu_t, mu_n, d_head_mm = inputs
108
+ r = results["results"]
109
+ v = results["verdict"]
110
+
111
+ system_prompt = (
112
+ "You explain engineering to a smart 5-year-old. "
113
+ "Use simple analogies like screwing a jar lid or tightening a bike seat. "
114
+ "You always return CONCISE responses, only one sentence."
115
+ )
116
+
117
+ user_prompt = (
118
+ f"For a bolt of diameter {d_mm:g} mm with a target preload of {F_N:g} N, "
119
+ f"thread pitch {p_mm:g} mm, thread friction {mu_t:g}, "
120
+ f"nut/under-head friction {mu_n:g}, and head diameter {d_head_mm:g} mm:\n"
121
+ f"The required torque is {r['torque_Nm']:.2f} Nm; "
122
+ "Explain this torque in ONE friendly sentence for a non-expert."
123
+ )
124
+
125
+ formatted = _format_chat(system_prompt, user_prompt)
126
+ return _llm_generate(formatted, max_tokens=128)
127
+
128
+ # Run everything together
129
+ def run_once(d_mm, F_N, p_mm, thread_material, thread_lubrication, head_material, head_lubrication, d_head_mm):
130
+ # Map dropdown selections to friction coefficients
131
+ mu_t = friction_table[thread_material][thread_lubrication]
132
+ mu_n = friction_table[head_material][head_lubrication]
133
+
134
+ if d_head_mm is None:
135
+ d_head_mm = float(d_mm) * 1.5
136
+
137
+ inputs = [float(d_mm), float(F_N), float(p_mm), mu_t, mu_n, float(d_head_mm)]
138
+ d = bolt_calc(
139
+ d_mm=float(d_mm),
140
+ F_N=float(F_N),
141
+ p_mm=float(p_mm),
142
+ mu_t=mu_t,
143
+ mu_n=mu_n,
144
+ d_head_mm=float(d_head_mm)
145
+ )
146
+
147
+ df = pandas.DataFrame([{
148
+ "Torque [Nm]": round(d["results"]["torque_Nm"], 3),
149
+ "Verdict": d["verdict"]["strength_message"],
150
+ }])
151
+
152
+ narrative = llm_explain(d, inputs).split("\n")[0]
153
+ return df, narrative
154
+
155
+ # Build the Gradio interface
156
+ with gradio.Blocks() as demo:
157
+
158
+ gradio.Markdown(
159
+ "# Bolt Torque Calculator"
160
+ )
161
+ gradio.Markdown(
162
+ "Compute the torque needed to tighten a bolt to a target preload with material and lubrication selection."
163
+ )
164
+
165
+ # Bolt geometry and load
166
+ with gradio.Row():
167
+ d_mm = gradio.Number(value=10.0, label="Bolt diameter [mm]")
168
+ F_N = gradio.Number(value=5000.0, label="Target preload [N]")
169
+ p_mm = gradio.Number(value=1.5, label="Thread pitch [mm]")
170
+
171
+ # Thread friction selection
172
+ with gradio.Row():
173
+ thread_material = gradio.Dropdown(
174
+ choices=["Steel", "Aluminum", "Brass", "Titanium"],
175
+ value="Steel",
176
+ label="Bolt material"
177
+ )
178
+ thread_lubrication = gradio.Dropdown(
179
+ choices=["Dry", "Oil", "Grease", "Zinc/Anti-seize"],
180
+ value="Dry",
181
+ label="Thread lubrication"
182
+ )
183
+ head_material = gradio.Dropdown(
184
+ choices=["Steel", "Aluminum", "Brass", "Titanium"],
185
+ value="Steel",
186
+ label="Nut/Head material"
187
+ )
188
+ head_lubrication = gradio.Dropdown(
189
+ choices=["Dry", "Oil", "Grease", "Zinc/Anti-seize"],
190
+ value="Dry",
191
+ label="Nut/Head lubrication"
192
+ )
193
+ d_head_mm = gradio.Number(value=None, label="Head diameter [mm] (optional)")
194
+
195
+ run_btn = gradio.Button("Compute")
196
+
197
+ results_df = gradio.Dataframe(label="Numerical results (deterministic)", interactive=False)
198
+ explain_md = gradio.Markdown(label="Explanation")
199
+
200
+ run_btn.click(
201
+ fn=run_once,
202
+ inputs=[d_mm, F_N, p_mm, thread_material, thread_lubrication, head_material, head_lubrication, d_head_mm],
203
+ outputs=[results_df, explain_md]
204
+ )
205
+
206
+ gradio.Examples(
207
+ examples=[
208
+ [10.0, 5000.0, 1.5, "Steel", "Dry", "Steel", "Dry", None],
209
+ [12.0, 8000.0, 1.75, "Aluminum", "Oil", "Steel", "Grease", None],
210
+ [8.0, 2000.0, 1.25, "Titanium", "Grease", "Aluminum", "Oil", None],
211
+ ],
212
+ inputs=[d_mm, F_N, p_mm, thread_material, thread_lubrication, head_material, head_lubrication, d_head_mm],
213
+ label="Representative cases",
214
+ examples_per_page=3,
215
+ cache_examples=False,
216
+ )
217
+
218
+ if __name__ == "__main__":
219
+ demo.launch(debug=True)