its-zion-18 commited on
Commit
7c497b8
·
verified ·
1 Parent(s): 5dd86ae

Create app.py

Browse files

Key Functionality and Components
The application is a combined mechanical calculator and a Hugging Face LLM inference engine deployed via Gradio.

1. Gear Dimension Calculator (gear_calc)
Purpose: Takes standard gear inputs—Number of Teeth (N), Diametral Pitch (Pd in teeth/inch), and Pressure Angle (ϕ in degrees)—and applies common mechanical engineering formulas (e.g., for Pitch Diameter, Base Diameter, Addendum, Dedendum, etc.).

Output: Returns a comprehensive dictionary of over 18 key gear dimensions, calculated and provided in both millimeters (mm) and inches (in).

2. Large Language Model (LLM) Integration
Model: Uses the HuggingFaceTB/SmolLM2-135M-Instruct model, a small, fast, instruction-following model suitable for low-latency tasks.

Inference: The transformers pipeline is initialized with deterministic settings (do_sample=False, temperature=0.0) to ensure the LLM provides the most compliant, non-creative, and factual response possible.

Recommendation (llm_explain): This function extracts key size parameters (Pitch Diameter and Circular Pitch) from the calculation results and uses a highly restrictive system prompt to force the LLM to output EXACTLY ONE CONCISE SENTENCE suggesting a general machinery category (e.g., "precision instrumentation," "heavy-duty power transmission").

3. Gradio Interface
Inputs: Three numerical input fields for the required gear parameters.

Process: The run_once function acts as the main entry point, handling input validation, running the calculation, formatting the numerical results into a Pandas DataFrame for a clean tabular display, and generating the LLM-based narrative.

Outputs: Displays the complete table of calculated dimensions and the LLM-generated single-sentence use case recommendation below it. The interface also includes pre-set examples for testing.

This tool demonstrates a multi-purpose application where traditional engineering computation is augmented by the interpretive and summarization capabilities of a small LLM.

Files changed (1) hide show
  1. app.py +264 -0
app.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math # Used for mathematical functions like radians and pi
2
+ import gradio as gr # Framework for building the web interface
3
+ import pandas as pd # Used for creating the structured output table (DataFrame)
4
+
5
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline # Core components for running the HuggingFace LLM
6
+
7
+
8
+ # Specifies the smaller, instructional model for low-latency recommendations
9
+ MODEL_ID = "HuggingFaceTB/SmolLM2-135M-Instruct"
10
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
11
+ pipe = pipeline(
12
+ task="text-generation",
13
+ model=AutoModelForCausalLM.from_pretrained(
14
+ MODEL_ID,
15
+ ),
16
+ tokenizer=tokenizer,
17
+ device=-1 # Runs on CPU by default; set device=0 for GPU
18
+ )
19
+
20
+ # 1. Calculation Function
21
+
22
+ def gear_calc(N_teeth: int, Pd: float, phi_deg: float) -> dict:
23
+ """
24
+ Calculates standard dimensions for an external spur gear based on common engineering formulas.
25
+
26
+ Inputs:
27
+ - N_teeth (int): Number of teeth
28
+ - Pd (float): Diametral Pitch [teeth/inch]
29
+ - phi_deg (float): Pressure Angle in degrees
30
+
31
+ Returns:
32
+ - dict: A dictionary containing all standard gear dimensions in both mm and inches.
33
+ """
34
+ if N_teeth <= 0 or Pd <= 0 or phi_deg <= 0:
35
+ # Input validation: all gear parameters must be positive
36
+ raise ValueError("All inputs must be positive.")
37
+
38
+ phi_rad = math.radians(phi_deg)
39
+
40
+ # Pitch Diameter (D) - The imaginary circle upon which the tooth spacing is measured.
41
+ pitch_diameter_in = N_teeth / Pd
42
+ pitch_diameter_mm = pitch_diameter_in * 25.4
43
+
44
+ # Addendum (a) and Dedendum (b) - Heights above and depths below the pitch circle.
45
+ addendum_in = 1 / Pd
46
+ dedendum_in = 1.25 / Pd
47
+ addendum_mm = addendum_in * 25.4
48
+ dedendum_mm = dedendum_in * 25.4
49
+
50
+ # Outside (OD) and Root Diameters - The largest and smallest diameters of the gear.
51
+ od_in = pitch_diameter_in + 2 * addendum_in
52
+ root_diameter_in = pitch_diameter_in - 2 * dedendum_in
53
+ od_mm = od_in * 25.4
54
+ root_diameter_mm = root_diameter_in * 25.4
55
+
56
+ # Working Depth and Whole Depth - Total contact depth and total tooth height.
57
+ working_depth_in = 2 * addendum_in
58
+ whole_depth_in = addendum_in + dedendum_in
59
+ working_depth_mm = working_depth_in * 25.4
60
+ whole_depth_mm = whole_depth_in * 25.4
61
+
62
+ # Circular Pitch (p) and Tooth Thickness (t) - Spacing and width of the tooth along the pitch circle.
63
+ circular_pitch_in = math.pi / Pd
64
+ circular_pitch_mm = circular_pitch_in * 25.4
65
+ tooth_thickness_in = circular_pitch_in / 2
66
+ tooth_thickness_mm = tooth_thickness_in * 25.4
67
+
68
+ # Base Diameter (Db) - The circle from which the involute curve is generated.
69
+ base_diameter_in = pitch_diameter_in * math.cos(phi_rad)
70
+ base_diameter_mm = base_diameter_in * 25.4
71
+
72
+ # Return a comprehensive dictionary of all calculated dimensions
73
+ return dict(
74
+ pitch_diameter_mm=pitch_diameter_mm,
75
+ pitch_diameter_in=pitch_diameter_in,
76
+ od_mm=od_mm,
77
+ od_in=od_in,
78
+ root_diameter_mm=root_diameter_mm,
79
+ root_diameter_in=root_diameter_in,
80
+ addendum_mm=addendum_mm,
81
+ addendum_in=addendum_in,
82
+ dedendum_mm=dedendum_mm,
83
+ dedendum_in=dedendum_in,
84
+ working_depth_mm=working_depth_mm,
85
+ working_depth_in=working_depth_in,
86
+ whole_depth_mm=whole_depth_mm,
87
+ whole_depth_in=whole_depth_in,
88
+ circular_pitch_mm=circular_pitch_mm,
89
+ circular_pitch_in=circular_pitch_in,
90
+ tooth_thickness_mm=tooth_thickness_mm,
91
+ tooth_thickness_in=tooth_thickness_in,
92
+ base_diameter_mm=base_diameter_mm,
93
+ base_diameter_in=base_diameter_in,
94
+ )
95
+
96
+
97
+ # 2. LLM Helper Functions
98
+
99
+ def _format_chat(system_prompt: str, user_prompt: str) -> str:
100
+ """
101
+ Helper function to structure the system and user prompts into the LLM's required
102
+ chat template format for optimal instruction adherence.
103
+ """
104
+ messages = [
105
+ {"role": "system", "content": system_prompt},
106
+ {"role": "user", "content": user_prompt},
107
+ ]
108
+ template = getattr(tokenizer, "chat_template", None)
109
+ return tokenizer.apply_chat_template(
110
+ messages,
111
+ tokenize=False,
112
+ add_generation_prompt=True
113
+ )
114
+
115
+ def _llm_generate(prompt: str, max_tokens: int) -> str:
116
+ """
117
+ Executes the text generation pipeline using deterministic settings (do_sample=False,
118
+ temperature=0.0) to ensure the LLM provides the most compliant, non-creative output.
119
+ """
120
+ out = pipe(
121
+ prompt,
122
+ max_new_tokens=max_tokens,
123
+ do_sample=False, # Disable random sampling for deterministic results
124
+ temperature=0.0, # Set temperature to 0.0 for maximum compliance
125
+ return_full_text=False,
126
+ )
127
+ # Strip leading/trailing whitespace from the generated text
128
+ return out[0]["generated_text"].strip()
129
+
130
+
131
+ # 3. LLM Explanation Function
132
+
133
+ def llm_explain(results: dict, inputs: list) -> str:
134
+ """
135
+ Generates a concise, single-sentence use case recommendation for the gear.
136
+ This function prepares the data and applies strict prompting to the LLM
137
+ to prevent rambling or lists.
138
+ """
139
+ # Unpack relevant calculated values for the LLM context
140
+ circular_pitch_in = results['circular_pitch_in']
141
+ pitch_diameter_in = results['pitch_diameter_in']
142
+
143
+ # Define the system prompt - Enforcing strict single-sentence output and content rules
144
+ system_prompt = (
145
+ "You are a mechanical engineer providing a design recommendation. "
146
+ "Your response MUST be **EXACTLY ONE CONCISE SENTENCE**. "
147
+ "Suggest ONE general category of machinery or application scale (e.g., 'precision instrumentation', 'heavy-duty mixer') for a gear "
148
+ "with these dimensions. **DO NOT** use bullet points, lists, or repeat input numbers."
149
+ )
150
+
151
+ # Define the user prompt - Providing the key size parameters
152
+ user_prompt = (
153
+ f"The gear has a Pitch Diameter of {pitch_diameter_in:.3f} inches and "
154
+ f"a Circular Pitch of {circular_pitch_in:.3f} inches. "
155
+ "Based only on these size parameters, provide its appropriate use case."
156
+ )
157
+
158
+ # Apply the chat template and generate text with a low token limit for brevity
159
+ formatted = _format_chat(system_prompt, user_prompt)
160
+ explanation = _llm_generate(formatted, max_tokens=80)
161
+
162
+ # Check if the LLM returned an empty string and provide a helpful fallback.
163
+ if not explanation:
164
+ explanation = "The language model did not return a use case. Please ensure your inputs are reasonable and try again."
165
+
166
+ return explanation
167
+
168
+
169
+ # 4. Main Entry Point for GUI
170
+
171
+ def run_once(N_teeth_str, Pd_str, phi_deg_str):
172
+ """
173
+ Main function executed by the Gradio interface. Handles input parsing,
174
+ calculation, result formatting (DataFrame), and LLM generation.
175
+ """
176
+ try:
177
+ # Input conversion from string to required types
178
+ N_teeth = int(N_teeth_str)
179
+ Pd = float(Pd_str)
180
+ phi_deg = float(phi_deg_str)
181
+ inputs = [N_teeth, Pd, phi_deg]
182
+
183
+ # Perform the core mechanical calculation
184
+ results = gear_calc(N_teeth, Pd, phi_deg)
185
+
186
+ # Prepare the data structure for the Pandas DataFrame output table
187
+ rows = [
188
+ # Each dictionary represents a row in the output table
189
+ {"Quantity": "Pitch Diameter", "Value (mm)": results["pitch_diameter_mm"], "Value (in)": results["pitch_diameter_in"]},
190
+ {"Quantity": "Outside Diameter (OD)", "Value (mm)": results["od_mm"], "Value (in)": results["od_in"]},
191
+ {"Quantity": "Root Diameter", "Value (mm)": results["root_diameter_mm"], "Value (in)": results["root_diameter_in"]},
192
+ {"Quantity": "Base Diameter", "Value (mm)": results["base_diameter_mm"], "Value (in)": results["base_diameter_in"]},
193
+ {"Quantity": "Addendum", "Value (mm)": results["addendum_mm"], "Value (in)": results["addendum_in"]},
194
+ {"Quantity": "Dedendum", "Value (mm)": results["dedendum_mm"], "Value (in)": results["dedendum_in"]},
195
+ {"Quantity": "Working Depth", "Value (mm)": results["working_depth_mm"], "Value (in)": results["working_depth_in"]},
196
+ {"Quantity": "Whole Depth", "Value (mm)": results["whole_depth_mm"], "Value (in)": results["whole_depth_in"]},
197
+ {"Quantity": "Circular Pitch", "Value (mm)": results["circular_pitch_mm"], "Value (in)": results["circular_pitch_in"]},
198
+ {"Quantity": "Tooth Thickness", "Value (mm)": results["tooth_thickness_mm"], "Value (in)": results["tooth_thickness_in"]},
199
+ ]
200
+
201
+ # Create and format the output DataFrame, rounding values for display
202
+ df = pd.DataFrame(rows)
203
+ df["Value (mm)"] = df["Value (mm)"].round(4)
204
+ df["Value (in)"] = df["Value (in)"].round(4)
205
+
206
+ # Generate the LLM narrative
207
+ narrative = llm_explain(results, inputs)
208
+
209
+ return df, narrative
210
+
211
+ except ValueError as e:
212
+ # Handle mathematical and input validation errors
213
+ error_df = pd.DataFrame([{"Error": str(e)}])
214
+ return error_df, "Error: Please ensure all inputs are valid positive numbers. Teeth must be an integer."
215
+ except Exception as e:
216
+ # Handle unexpected system/LLM errors
217
+ error_df = pd.DataFrame([{"System Error": "Calculation or LLM failed"}])
218
+ print(f"Internal Error: {e}")
219
+ return error_df, "An unexpected system error occurred during computation or LLM generation."
220
+
221
+
222
+ # Gradio UI Definition
223
+
224
+ with gr.Blocks() as demo:
225
+
226
+ # App Title and Description
227
+ gr.Markdown(
228
+ "# External Gear Dimension Calculator ⚙️"
229
+ )
230
+ gr.Markdown(
231
+ "Enter number of teeth, diametral pitch, and pressure angle to get standard external gear dimensions, and an LLM-generated use case."
232
+ )
233
+
234
+ # Input parameters organized in a row layout
235
+ with gr.Row():
236
+ N_teeth_in = gr.Number(value=0, label="Number of Teeth (#)", precision=0)
237
+ Pd_in = gr.Number(value=0, label="Diametral Pitch [teeth/inch]")
238
+ phi_deg_in = gr.Number(value=0, label="Pressure Angle [°]")
239
+
240
+ # Action button to trigger the process
241
+ run_btn = gr.Button("Calculate and Explain Gear Use")
242
+
243
+ # Output components: DataFrame for numbers, Markdown for LLM text
244
+ results_df = gr.Dataframe(label="Gear Dimensions (mm and inches)", interactive=False)
245
+ explain_md = gr.Markdown(label="LLM-Generated Use Case")
246
+
247
+ # Event listener: Connect the button click to the main computation function
248
+ run_btn.click(fn=run_once, inputs=[N_teeth_in, Pd_in, phi_deg_in], outputs=[results_df, explain_md])
249
+
250
+ # Pre-defined examples for easy testing
251
+ gr.Examples(
252
+ examples=[
253
+ [12, 4.0, 20.0],
254
+ [40, 0.75, 45.0],
255
+ [200, 0.5, 25.0],
256
+ ],
257
+ inputs=[N_teeth_in, Pd_in, phi_deg_in],
258
+ label="Representative Gear Cases",
259
+ examples_per_page=3,
260
+ cache_examples=False,
261
+ )
262
+
263
+ if __name__ == "__main__":
264
+ demo.launch(debug=True)