Usmansafdarktk commited on
Commit
f0a2335
·
1 Parent(s): d011912

Adding updated datasets and updated app.py and template code UI

Browse files
Files changed (37) hide show
  1. app.py +16 -11
  2. data/templates/branches/chemical_engineering/__pycache__/constants.cpython-311.pyc +0 -0
  3. data/templates/branches/chemical_engineering/constants.py +95 -46
  4. data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/conversion_and_reactor_sizing.cpython-311.pyc +0 -0
  5. data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/mole_balances.cpython-311.pyc +0 -0
  6. data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/stoichiometry.cpython-311.pyc +0 -0
  7. data/templates/branches/chemical_engineering/reaction_kinetics/stoichiometry.py +200 -173
  8. data/templates/branches/chemical_engineering/thermodynamics/__pycache__/heat_effects.cpython-311.pyc +0 -0
  9. data/templates/branches/chemical_engineering/thermodynamics/__pycache__/volumetric_properties_pure_fluids.cpython-311.pyc +0 -0
  10. data/templates/branches/chemical_engineering/thermodynamics/heat_effects.py +143 -97
  11. data/templates/branches/chemical_engineering/thermodynamics/volumetric_properties_pure_fluids.py +78 -75
  12. data/templates/branches/chemical_engineering/transport_phenomena/__pycache__/shell_momentum_balances.cpython-311.pyc +0 -0
  13. data/templates/branches/chemical_engineering/transport_phenomena/__pycache__/viscosity_and_momentum_transport.cpython-311.pyc +0 -0
  14. data/templates/branches/chemical_engineering/transport_phenomena/shell_momentum_balances.py +126 -87
  15. data/templates/branches/chemical_engineering/transport_phenomena/viscosity_and_momentum_transport.py +6 -3
  16. data/templates/branches/electrical_engineering/__pycache__/constants.cpython-311.pyc +0 -0
  17. data/templates/branches/electrical_engineering/digital_communications/__pycache__/deterministic_and_random_signal_analysis.cpython-311.pyc +0 -0
  18. data/templates/branches/electrical_engineering/digital_communications/__pycache__/digital_modulation_schemes.cpython-311.pyc +0 -0
  19. data/templates/branches/electrical_engineering/digital_communications/digital_modulation_schemes.py +57 -59
  20. data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/electrostatics.cpython-311.pyc +0 -0
  21. data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/magnetostatics.cpython-311.pyc +0 -0
  22. data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/waves_and_phasors.cpython-311.pyc +0 -0
  23. data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/continuous_time_signals.cpython-311.pyc +0 -0
  24. data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/discrete_time_signals.cpython-311.pyc +0 -0
  25. data/templates/branches/electrical_engineering/signals_and_systems/discrete_time_signals.py +99 -84
  26. data/templates/branches/mechanical_engineering/__pycache__/constants.cpython-311.pyc +0 -0
  27. data/templates/branches/mechanical_engineering/fluid_mechanics/__pycache__/fluid_kinematics.cpython-311.pyc +0 -0
  28. data/templates/branches/mechanical_engineering/fluid_mechanics/__pycache__/fluid_statics.cpython-311.pyc +0 -0
  29. data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_kinematics.py +115 -71
  30. data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_statics.py +85 -64
  31. data/templates/branches/mechanical_engineering/mechanics_of_materials/__pycache__/stress_and_strain.cpython-311.pyc +0 -0
  32. data/templates/branches/mechanical_engineering/mechanics_of_materials/__pycache__/torsion.cpython-311.pyc +0 -0
  33. data/templates/branches/mechanical_engineering/mechanics_of_materials/stress_and_strain.py +145 -102
  34. data/templates/branches/mechanical_engineering/mechanics_of_materials/torsion.py +62 -55
  35. data/templates/branches/mechanical_engineering/vibrations_and_acoustics/__pycache__/harmonically_excited_vibrations.cpython-311.pyc +0 -0
  36. data/templates/branches/mechanical_engineering/vibrations_and_acoustics/__pycache__/single_degree_of_freedom_systems.cpython-311.pyc +0 -0
  37. src/template_loader.py +64 -37
app.py CHANGED
@@ -10,20 +10,25 @@ from src.template_loader import (
10
  )
11
  from src.storage import save_review
12
 
13
- # --- Page Configuration ---
14
  st.set_page_config(layout="wide", page_title="EngChain Annotator")
15
 
16
- # --- Custom CSS for Styling ---
17
  # This forces the primary buttons to have a specific look (optional but helps visibility)
18
  st.markdown("""
19
  <style>
20
  div.stButton > button:first-child {
21
  font-weight: bold;
22
  }
 
 
 
 
 
23
  </style>
24
  """, unsafe_allow_html=True)
25
 
26
- # --- Helper Function: Build the Queue ---
27
  def build_review_queue(branch):
28
  """
29
  Scans the entire branch and creates a list of all templates to review.
@@ -59,7 +64,7 @@ def build_review_queue(branch):
59
  progress_bar.empty()
60
  return queue
61
 
62
- # --- Session State Initialization ---
63
  if "app_mode" not in st.session_state:
64
  st.session_state["app_mode"] = "landing" # landing, review, done
65
  if "review_queue" not in st.session_state:
@@ -92,7 +97,7 @@ if st.session_state["app_mode"] == "landing":
92
  5. Rate it, Approve/Reject it, and click **Submit**.
93
  """)
94
 
95
- st.markdown("---")
96
 
97
  with st.form("onboarding_form"):
98
  st.subheader("Annotator Details")
@@ -139,12 +144,12 @@ elif st.session_state["app_mode"] == "review":
139
 
140
  current_item = queue[idx]
141
 
142
- # --- Sidebar Info ---
143
  st.sidebar.title("Progress")
144
  st.sidebar.progress((idx) / len(queue))
145
  st.sidebar.write(f"Template: {idx + 1} / {len(queue)}")
146
 
147
- st.sidebar.markdown("---")
148
  st.sidebar.subheader("Current Context")
149
 
150
  # Distinct styling for keys and values
@@ -157,14 +162,14 @@ elif st.session_state["app_mode"] == "review":
157
  st.sidebar.markdown("**🧩 Function:**")
158
  st.sidebar.info(current_item['name'])
159
 
160
- # --- Main Content ---
161
  st.title(f"Reviewing: {current_item['name']}")
162
 
163
  # Tabs for Inspection
164
  tab1, tab2 = st.tabs(["Source Code", "Generated Trace"])
165
 
166
  with tab1:
167
- code_text = get_source_code(current_item['branch'], current_item['area'], current_item['file'])
168
  st.code(code_text, language="python", line_numbers=True)
169
 
170
  with tab2:
@@ -183,9 +188,9 @@ elif st.session_state["app_mode"] == "review":
183
  st.error("Template Execution Failed")
184
  st.code(traceback.format_exc())
185
 
186
- st.markdown("---")
187
 
188
- # --- Scoring Form ---
189
  st.subheader("Audit Decision")
190
 
191
  # Use a container so we can disable the form after submission
 
10
  )
11
  from src.storage import save_review
12
 
13
+ # Page Configuration
14
  st.set_page_config(layout="wide", page_title="EngChain Annotator")
15
 
16
+ # Custom CSS for Styling
17
  # This forces the primary buttons to have a specific look (optional but helps visibility)
18
  st.markdown("""
19
  <style>
20
  div.stButton > button:first-child {
21
  font-weight: bold;
22
  }
23
+ /* FIX: Lock sidebar position and disable scrolling */
24
+ section[data-testid="stSidebar"] {
25
+ position: fixed;
26
+ overflow: hidden !important;
27
+ }
28
  </style>
29
  """, unsafe_allow_html=True)
30
 
31
+ # Helper Function: Build the Queue
32
  def build_review_queue(branch):
33
  """
34
  Scans the entire branch and creates a list of all templates to review.
 
64
  progress_bar.empty()
65
  return queue
66
 
67
+ # Session State Initialization
68
  if "app_mode" not in st.session_state:
69
  st.session_state["app_mode"] = "landing" # landing, review, done
70
  if "review_queue" not in st.session_state:
 
97
  5. Rate it, Approve/Reject it, and click **Submit**.
98
  """)
99
 
100
+ st.markdown("")
101
 
102
  with st.form("onboarding_form"):
103
  st.subheader("Annotator Details")
 
144
 
145
  current_item = queue[idx]
146
 
147
+ # Sidebar Info
148
  st.sidebar.title("Progress")
149
  st.sidebar.progress((idx) / len(queue))
150
  st.sidebar.write(f"Template: {idx + 1} / {len(queue)}")
151
 
152
+ st.sidebar.markdown("")
153
  st.sidebar.subheader("Current Context")
154
 
155
  # Distinct styling for keys and values
 
162
  st.sidebar.markdown("**🧩 Function:**")
163
  st.sidebar.info(current_item['name'])
164
 
165
+ # Main Content
166
  st.title(f"Reviewing: {current_item['name']}")
167
 
168
  # Tabs for Inspection
169
  tab1, tab2 = st.tabs(["Source Code", "Generated Trace"])
170
 
171
  with tab1:
172
+ code_text = get_source_code(current_item['func'])
173
  st.code(code_text, language="python", line_numbers=True)
174
 
175
  with tab2:
 
188
  st.error("Template Execution Failed")
189
  st.code(traceback.format_exc())
190
 
191
+ st.markdown("")
192
 
193
+ # Scoring Form
194
  st.subheader("Audit Decision")
195
 
196
  # Use a container so we can disable the form after submission
data/templates/branches/chemical_engineering/__pycache__/constants.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/chemical_engineering/__pycache__/constants.cpython-311.pyc and b/data/templates/branches/chemical_engineering/__pycache__/constants.cpython-311.pyc differ
 
data/templates/branches/chemical_engineering/constants.py CHANGED
@@ -186,6 +186,46 @@ THERMO_SUBSTANCES = [
186
  ]
187
 
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  # A dictionary with comprehensive critical properties for various substances.
190
  # Tc: Kelvin (K), Pc: bar, Vc: cm³/mol, Zc: dimensionless, omega: dimensionless.
191
  CRITICAL_PROPERTIES = {
@@ -220,47 +260,50 @@ CRITICAL_PROPERTIES = {
220
 
221
 
222
  # Common materials which undergo heating with their specific heat capacities in J/g·K
 
223
  SUBSTANCES_FOR_HEATING = [
224
- # Metals & Solids
225
- {"name": "Iron", "state": "solid", "Cp": 0.449},
226
- {"name": "Copper", "state": "solid", "Cp": 0.385},
227
- {"name": "Aluminum", "state": "solid", "Cp": 0.897},
228
- {"name": "Gold", "state": "solid", "Cp": 0.129},
229
- {"name": "Lead", "state": "solid", "Cp": 0.16},
230
- {"name": "Silver", "state": "solid", "Cp": 0.235},
231
- {"name": "Tungsten", "state": "solid", "Cp": 0.134},
232
- {"name": "Silicon", "state": "solid", "Cp": 0.705},
233
- {"name": "Graphite (Carbon)", "state": "solid", "Cp": 0.709},
234
- {"name": "Glass (typical)", "state": "solid", "Cp": 0.84},
235
- {"name": "Ice (at 0°C)", "state": "solid", "Cp": 2.09},
236
- {"name": "Concrete", "state": "solid", "Cp": 0.88},
237
- {"name": "Wood (typical)", "state": "solid", "Cp": 1.7},
238
- {"name": "Polyethylene (plastic)", "state": "solid", "Cp": 2.3},
239
-
240
- # Liquids
241
- {"name": "Water", "state": "liquid", "Cp": 4.18},
242
- {"name": "Ethanol", "state": "liquid", "Cp": 2.44},
243
- {"name": "Methanol", "state": "liquid", "Cp": 2.53},
244
- {"name": "Acetone", "state": "liquid", "Cp": 2.17},
245
- {"name": "Mercury", "state": "liquid", "Cp": 0.14},
246
- {"name": "Glycerol", "state": "liquid", "Cp": 2.43},
247
- {"name": "Ethylene Glycol (Antifreeze)", "state": "liquid", "Cp": 2.36},
248
- {"name": "Olive Oil", "state": "liquid", "Cp": 1.97},
249
- {"name": "Engine Oil (typical)", "state": "liquid", "Cp": 1.9},
250
- {"name": "Sulfuric Acid", "state": "liquid", "Cp": 1.42},
251
-
252
- # Gases (at constant pressure, 25°C)
253
- {"name": "Air (dry)", "state": "gas", "Cp": 1.005},
254
- {"name": "Nitrogen", "state": "gas", "Cp": 1.04},
255
- {"name": "Oxygen", "state": "gas", "Cp": 0.918},
256
- {"name": "Hydrogen", "state": "gas", "Cp": 14.31},
257
- {"name": "Helium", "state": "gas", "Cp": 5.193},
258
- {"name": "Argon", "state": "gas", "Cp": 0.520},
259
- {"name": "Carbon Dioxide", "state": "gas", "Cp": 0.839},
260
- {"name": "Methane", "state": "gas", "Cp": 2.22},
261
- {"name": "Ammonia", "state": "gas", "Cp": 2.06},
262
- {"name": "Water Vapor (Steam, 100°C)", "state": "gas", "Cp": 2.01},
263
- {"name": "Chlorine", "state": "gas", "Cp": 0.48}
 
 
264
  ]
265
 
266
 
@@ -294,16 +337,22 @@ SUBSTANCES_FOR_VAPORIZATION = [
294
 
295
 
296
  # Database of standard heats of formation (ΔH_f°) at 298.15 K in kJ/mol.
297
- # A value of 0 indicates an element in its standard state.
298
  HEATS_OF_FORMATION = {
299
- # Hydrocarbons
300
  "CH4(g)": -74.8, # Methane
 
301
  "C2H6(g)": -84.7, # Ethane
302
  "C3H8(g)": -103.8, # Propane
 
 
303
  "C6H6(l)": 49.0, # Benzene
304
- # Alcohols
305
- "CH3OH(l)": -238.6, # Methanol
306
- "C2H5OH(l)": -277.7, # Ethanol
 
 
 
 
307
  # Common Gases & Products
308
  "O2(g)": 0,
309
  "H2(g)": 0,
@@ -312,7 +361,7 @@ HEATS_OF_FORMATION = {
312
  "CO2(g)": -393.5,
313
  "H2O(g)": -241.8,
314
  "H2O(l)": -285.8,
315
- "NH3(g)": -46.1, # Ammonia
316
  "NO(g)": 90.3,
317
  "NO2(g)": 33.2,
318
  }
 
186
  ]
187
 
188
 
189
+ # A dictionary of real-world saturation properties for common thermodynamic substances.
190
+ # Each entry contains: temperature (°C), specific volume of saturated liquid (m³/kg),
191
+ # and specific volume of saturated vapor (m³/kg)
192
+ # Data sources: NIST WebBook, Engineering Toolbox, and standard thermodynamic tables.
193
+ REAL_FLUID_DATA = {
194
+ # Classic Working Fluids
195
+ "Water": {"temp_C": 100, "v_f": 0.001043, "v_g": 1.6729},
196
+ "Ammonia": {"temp_C": 25, "v_f": 0.001658, "v_g": 0.1284},
197
+ "Carbon Dioxide": {"temp_C": 20, "v_f": 0.001286, "v_g": 0.0188},
198
+ "Sulfur Dioxide": {"temp_C": 25, "v_f": 0.000728, "v_g": 0.1165},
199
+
200
+ # Hydrocarbons
201
+ "Methane": {"temp_C": -161, "v_f": 0.002367, "v_g": 1.8160}, # Boiling Point
202
+ "Ethane": {"temp_C": -89, "v_f": 0.001830, "v_g": 0.7090}, # Boiling Point
203
+ "Propane": {"temp_C": 25, "v_f": 0.002028, "v_g": 0.0461},
204
+ "Butane": {"temp_C": 25, "v_f": 0.001726, "v_g": 0.1632},
205
+ "Isobutane": {"temp_C": 25, "v_f": 0.001815, "v_g": 0.1118},
206
+ "Pentane": {"temp_C": 25, "v_f": 0.001598, "v_g": 0.5050},
207
+ "Iso-pentane": {"temp_C": 25, "v_f": 0.001614, "v_g": 0.4910},
208
+
209
+ # Refrigerants
210
+ "Refrigerant-11 (R-11, Trichlorofluoromethane)": {"temp_C": 25, "v_f": 0.000675, "v_g": 0.1580},
211
+ "Refrigerant-12 (R-12, Dichlorodifluoromethane)": {"temp_C": 25, "v_f": 0.000763, "v_g": 0.0268},
212
+ "Refrigerant-22 (R-22, Chlorodifluoromethane)": {"temp_C": 25, "v_f": 0.000845, "v_g": 0.0226},
213
+ "Refrigerant-134a (R-134a, 1,1,1,2-Tetrafluoroethane)": {"temp_C": 25, "v_f": 0.000829, "v_g": 0.0323},
214
+ "Refrigerant-123 (R-123, Dichlorotrifluoroethane)": {"temp_C": 25, "v_f": 0.000680, "v_g": 0.1810},
215
+ "Refrigerant-410A (R-410A, blend of difluoromethane and pentafluoroethane)": {"temp_C": 25, "v_f": 0.000922, "v_g": 0.0151},
216
+
217
+ # Organic Solvents (Rankine Fluids)
218
+ "Toluene": {"temp_C": 111, "v_f": 0.001308, "v_g": 0.3200}, # Boiling Point
219
+ "Benzene": {"temp_C": 80, "v_f": 0.001205, "v_g": 0.3660}, # Boiling Point
220
+ "Ethanol": {"temp_C": 78, "v_f": 0.001290, "v_g": 0.5770}, # Boiling Point
221
+ "Methanol": {"temp_C": 65, "v_f": 0.001375, "v_g": 0.8800}, # Boiling Point
222
+ "Acetone": {"temp_C": 56, "v_f": 0.001360, "v_g": 0.5370}, # Boiling Point
223
+ "n-Hexane": {"temp_C": 69, "v_f": 0.001660, "v_g": 0.3550}, # Boiling Point
224
+ "n-Octane": {"temp_C": 126, "v_f": 0.001690, "v_g": 0.2500}, # Boiling Point
225
+ "Cyclohexane": {"temp_C": 81, "v_f": 0.001420, "v_g": 0.3600} # Boiling Point
226
+ }
227
+
228
+
229
  # A dictionary with comprehensive critical properties for various substances.
230
  # Tc: Kelvin (K), Pc: bar, Vc: cm³/mol, Zc: dimensionless, omega: dimensionless.
231
  CRITICAL_PROPERTIES = {
 
260
 
261
 
262
  # Common materials which undergo heating with their specific heat capacities in J/g·K
263
+ # Added 'min_temp' and 'max_temp' (in °C) to ensure phase stability.
264
  SUBSTANCES_FOR_HEATING = [
265
+ # Metals & Solids (Generally safe 20°C - 500°C)
266
+ {"name": "Iron", "state": "solid", "Cp": 0.449, "min_temp": 20, "max_temp": 500},
267
+ {"name": "Copper", "state": "solid", "Cp": 0.385, "min_temp": 20, "max_temp": 500},
268
+ {"name": "Aluminum", "state": "solid", "Cp": 0.897, "min_temp": 20, "max_temp": 500},
269
+ {"name": "Gold", "state": "solid", "Cp": 0.129, "min_temp": 20, "max_temp": 500},
270
+ {"name": "Lead", "state": "solid", "Cp": 0.16, "min_temp": 20, "max_temp": 300},
271
+ {"name": "Silver", "state": "solid", "Cp": 0.235, "min_temp": 20, "max_temp": 500},
272
+ {"name": "Tungsten", "state": "solid", "Cp": 0.134, "min_temp": 20, "max_temp": 1000},
273
+ {"name": "Silicon", "state": "solid", "Cp": 0.705, "min_temp": 20, "max_temp": 500},
274
+ {"name": "Graphite (Carbon)", "state": "solid", "Cp": 0.709, "min_temp": 20, "max_temp": 1000},
275
+ {"name": "Glass (typical)", "state": "solid", "Cp": 0.84, "min_temp": 20, "max_temp": 500},
276
+ {"name": "Concrete", "state": "solid", "Cp": 0.88, "min_temp": 20, "max_temp": 500},
277
+ {"name": "Wood (typical)", "state": "solid", "Cp": 1.7, "min_temp": 20, "max_temp": 150},
278
+ {"name": "Polyethylene (plastic)", "state": "solid", "Cp": 2.3, "min_temp": 20, "max_temp": 100}, # Melts ~115°C
279
+
280
+ # Phase-Sensitive Solids
281
+ {"name": "Ice (at 0°C)", "state": "solid", "Cp": 2.09, "min_temp": -50, "max_temp": -2}, # Must be < 0°C
282
+
283
+ # Liquids (Must stay between Freezing and Boiling Points)
284
+ {"name": "Water", "state": "liquid", "Cp": 4.18, "min_temp": 5, "max_temp": 95},
285
+ {"name": "Ethanol", "state": "liquid", "Cp": 2.44, "min_temp": -50, "max_temp": 75}, # Boils at 78°C
286
+ {"name": "Methanol", "state": "liquid", "Cp": 2.53, "min_temp": -50, "max_temp": 60}, # Boils at 65°C
287
+ {"name": "Acetone", "state": "liquid", "Cp": 2.17, "min_temp": -50, "max_temp": 50}, # Boils at 56°C
288
+ {"name": "Mercury", "state": "liquid", "Cp": 0.14, "min_temp": -30, "max_temp": 300},
289
+ {"name": "Glycerol", "state": "liquid", "Cp": 2.43, "min_temp": 20, "max_temp": 250},
290
+ {"name": "Ethylene Glycol (Antifreeze)", "state": "liquid", "Cp": 2.36, "min_temp": -10, "max_temp": 190},
291
+ {"name": "Olive Oil", "state": "liquid", "Cp": 1.97, "min_temp": 20, "max_temp": 200},
292
+ {"name": "Engine Oil (typical)", "state": "liquid", "Cp": 1.9, "min_temp": 20, "max_temp": 250},
293
+ {"name": "Sulfuric Acid", "state": "liquid", "Cp": 1.42, "min_temp": 20, "max_temp": 300},
294
+
295
+ # Gases (Wide range, but avoid condensation for vapors)
296
+ {"name": "Air (dry)", "state": "gas", "Cp": 1.005, "min_temp": -50, "max_temp": 500},
297
+ {"name": "Nitrogen", "state": "gas", "Cp": 1.04, "min_temp": -100, "max_temp": 500},
298
+ {"name": "Oxygen", "state": "gas", "Cp": 0.918, "min_temp": -100, "max_temp": 500},
299
+ {"name": "Hydrogen", "state": "gas", "Cp": 14.31, "min_temp": -100, "max_temp": 500},
300
+ {"name": "Helium", "state": "gas", "Cp": 5.193, "min_temp": -200, "max_temp": 500},
301
+ {"name": "Argon", "state": "gas", "Cp": 0.520, "min_temp": -100, "max_temp": 500},
302
+ {"name": "Carbon Dioxide", "state": "gas", "Cp": 0.839, "min_temp": -50, "max_temp": 500},
303
+ {"name": "Methane", "state": "gas", "Cp": 2.22, "min_temp": -100, "max_temp": 500},
304
+ {"name": "Ammonia", "state": "gas", "Cp": 2.06, "min_temp": -20, "max_temp": 400},
305
+ {"name": "Water Vapor (Steam)", "state": "gas", "Cp": 2.01, "min_temp": 105, "max_temp": 400}, # Must be > 100°C
306
+ {"name": "Chlorine", "state": "gas", "Cp": 0.48, "min_temp": 20, "max_temp": 200}
307
  ]
308
 
309
 
 
337
 
338
 
339
  # Database of standard heats of formation (ΔH_f°) at 298.15 K in kJ/mol.
 
340
  HEATS_OF_FORMATION = {
341
+ # Hydrocarbons (Gases)
342
  "CH4(g)": -74.8, # Methane
343
+ "C2H2(g)": 226.7, # Acetylene (Added)
344
  "C2H6(g)": -84.7, # Ethane
345
  "C3H8(g)": -103.8, # Propane
346
+ "C4H10(g)": -125.7, # Butane (Added)
347
+ "C8H18(g)": -208.4, # Octane (Gas phase) (Added)
348
  "C6H6(l)": 49.0, # Benzene
349
+
350
+ # Alcohols (Liquids & Gases)
351
+ "CH3OH(l)": -238.6, # Methanol (Liquid)
352
+ "CH3OH(g)": -200.7, # Methanol (Gas) (Added - required for adiabatic flame temp)
353
+ "C2H5OH(l)": -277.7, # Ethanol (Liquid)
354
+ "C2H5OH(g)": -235.1, # Ethanol (Gas) (Added - required for adiabatic flame temp)
355
+
356
  # Common Gases & Products
357
  "O2(g)": 0,
358
  "H2(g)": 0,
 
361
  "CO2(g)": -393.5,
362
  "H2O(g)": -241.8,
363
  "H2O(l)": -285.8,
364
+ "NH3(g)": -46.1,
365
  "NO(g)": 90.3,
366
  "NO2(g)": 33.2,
367
  }
data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/conversion_and_reactor_sizing.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/conversion_and_reactor_sizing.cpython-311.pyc and b/data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/conversion_and_reactor_sizing.cpython-311.pyc differ
 
data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/mole_balances.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/mole_balances.cpython-311.pyc and b/data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/mole_balances.cpython-311.pyc differ
 
data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/stoichiometry.cpython-311.pyc ADDED
Binary file (30.4 kB). View file
 
data/templates/branches/chemical_engineering/reaction_kinetics/stoichiometry.py CHANGED
@@ -1,5 +1,5 @@
1
  import random
2
- from data.templates.branches.chemical_engineering.constants import GENERAL_REACTANTS, LIQUID_PHASE_REACTANTS, GAS_PHASE_REACTANTS, PRODUCTS
3
 
4
 
5
  # Template 1 (Easy)
@@ -23,25 +23,37 @@ def template_batch_moles_vs_conversion():
23
  - str: A question about calculating final moles in a batch reactor.
24
  - str: A detailed, step-by-step solution.
25
  """
26
- # 1. Randomized Parameters
27
-
28
- # Select names for reactants and products
29
- reactant_A_name, reactant_B_name = random.sample(LIQUID_PHASE_REACTANTS, 2)
30
- product_C_name, product_D_name = random.sample(PRODUCTS, 2)
 
 
 
 
 
 
 
 
31
 
32
- # Generate stoichiometric coefficients
33
- a = random.choice([1, 2])
34
- b = random.randint(1, 3)
35
- c = random.randint(1, 3)
36
- d = random.randint(1, 3)
 
 
37
 
38
- # Generate initial moles, ensuring A is the limiting reactant
 
39
  N_A0 = round(random.uniform(10.0, 25.0), 2)
40
- # Ensure B is in excess (at least 20% more than stoichiometrically required)
41
- N_B0 = round((N_A0 * b / a) * random.uniform(1.2, 2.0), 2)
42
- # Initial moles of products are often zero but can be non-zero
43
- N_C0 = round(random.choice([0.0, random.uniform(1.0, 5.0)]), 2)
44
- N_D0 = round(random.choice([0.0, random.uniform(1.0, 5.0)]), 2)
 
45
 
46
  # Generate a realistic conversion for the limiting reactant A
47
  X_A = round(random.uniform(0.40, 0.95), 2)
@@ -50,63 +62,63 @@ def template_batch_moles_vs_conversion():
50
  N_A = N_A0 * (1 - X_A)
51
  N_B = N_B0 - (b / a) * N_A0 * X_A
52
  N_C = N_C0 + (c / a) * N_A0 * X_A
53
- N_D = N_D0 + (d / a) * N_A0 * X_A
54
 
55
  # 3. Generate Question and Solution Strings
56
-
57
- # Helper function to format the reaction string cleanly (e.g., "A" instead of "1A")
58
- def format_species(coeff, name):
59
- return f"{coeff if coeff > 1 else ''}{name}"
60
-
61
- reaction_string = (
62
- f"{format_species(a, reactant_A_name)} + {format_species(b, reactant_B_name)} -> "
63
- f"{format_species(c, product_C_name)} + {format_species(d, product_D_name)}"
64
- )
65
-
66
  question = (
67
- f"Consider the following elementary liquid-phase reaction carried out in a batch reactor:\n"
68
- f"**Reaction:** ${reaction_string}$\n\n"
69
- f"The reactor is initially charged with ${N_A0}$ moles of {reactant_A_name}, "
70
- f"${N_B0}$ moles of {reactant_B_name}, and ${N_C0}$ moles of {product_C_name}. "
71
  f"{reactant_A_name} is the limiting reactant.\n\n"
72
- f"If the reaction is allowed to proceed until a conversion of ${X_A}$ ($_A$) is achieved, "
73
  f"calculate the final number of moles of all species in the reactor."
74
  )
75
 
76
  solution = (
77
  f"**Step 1:** Identify Given Information\n"
78
- f"- Reaction: ${reaction_string}$\n"
79
  f"- Initial Moles:\n"
80
  f" - $N_{{{reactant_A_name},0}} = {N_A0}$ mol\n"
81
  f" - $N_{{{reactant_B_name},0}} = {N_B0}$ mol\n"
82
- f" - $N_{{{product_C_name},0}} = {N_C0}$ mol\n"
83
- f" - $N_{{{product_D_name},0}} = {N_D0}$ mol\n"
84
  f"- Conversion of {reactant_A_name}: $X_A = {X_A}$\n\n"
85
 
86
  f"**Step 2:** Write Stoichiometric Relations in Terms of Conversion\n"
87
- f"The number of moles of each species ($N_j$) at any conversion $X_A$ can be expressed using the following design equations for a batch system:\n"
88
  f"- $N_A = N_{{A,0}}(1 - X_A)$\n"
89
- f"- $N_B = N_{{B,0}} - (b/a) N_{{A,0}} X_A$\n"
90
- f"- $N_C = N_{{C,0}} + (c/a) N_{{A,0}} X_A$\n"
91
- f"- $N_D = N_{{D,0}} + (d/a) N_{{A,0}} X_A$\n\n"
 
 
 
92
 
 
93
  f"**Step 3:** Calculate Final Moles for Each Species\n"
94
  f"For {reactant_A_name} (A):\n"
95
- f"$N_A = {N_A0}(1 - {X_A}) = {N_A0}({1-X_A}) = {round(N_A, 3)}$ mol\n\n"
96
  f"For {reactant_B_name} (B):\n"
97
  f"$N_B = {N_B0} - ({b}/{a}) \\times {N_A0} \\times {X_A} = {N_B0} - {round((b/a)*N_A0*X_A, 3)} = {round(N_B, 3)}$ mol\n\n"
98
  f"For {product_C_name} (C):\n"
99
- f"$N_C = {N_C0} + ({c}/{a}) \\times {N_A0} \\times {X_A} = {N_C0} + {round((c/a)*N_A0*X_A, 3)} = {round(N_C, 3)}$ mol\n\n"
100
- f"For {product_D_name} (D):\n"
101
- f"$N_D = {N_D0} + ({d}/{a}) \\times {N_A0} \\times {X_A} = {N_D0} + {round((d/a)*N_A0*X_A, 3)} = {round(N_D, 3)}$ mol\n\n"
 
 
 
 
 
102
 
 
103
  f"**Final Answer**\n"
104
  f"After reaching a conversion of ${X_A*100} \%$, the final number of moles in the reactor are:\n"
105
  f"- {reactant_A_name}: ${round(N_A, 3)}$ mol\n"
106
  f"- {reactant_B_name}: ${round(N_B, 3)}$ mol\n"
107
  f"- {product_C_name}: ${round(N_C, 3)}$ mol\n"
108
- f"- {product_D_name}: ${round(N_D, 3)}$ mol"
109
  )
 
 
110
 
111
  return question, solution
112
 
@@ -122,31 +134,46 @@ def template_flow_system_molar_flow_rates():
122
  inlet flow rates and the conversion of a limiting reactant. The calculation
123
  is an application of stoichiometric principles to a flow system, using:
124
 
125
- $F_A = F_{A0}(1 - X_A)$
126
- $F_j = F_{j0} + \\nu_j F_{A0} X_A = F_{A0}(\\Theta_j + \\nu_j X_A)$
127
 
128
- where $\\nu_j$ is the stoichiometric coefficient and $\\Theta_j = F_{j0}/F_{A0}$.
129
 
130
  Returns:
131
  tuple: A tuple containing:
132
  - str: A question about calculating outlet molar flow rates.
133
  - str: A detailed, step-by-step solution.
134
  """
135
- # 1. Randomized Parameters
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
- # Select unique names for reactants and products
138
- reactant_A_name, reactant_B_name = random.sample(GENERAL_REACTANTS, 2)
139
- product_C_name, product_D_name = random.sample(PRODUCTS, 2)
140
-
141
- # Generate stoichiometric coefficients
142
- a = random.choice([1, 2])
143
- b = random.randint(1, 3)
144
- c = random.randint(1, 3)
145
- d = random.randint(1, 3)
146
 
147
- # Generate inlet molar flow rates (mol/min), ensuring A is limiting
 
148
  F_A0 = round(random.uniform(50.0, 150.0), 2)
149
- F_B0 = round((F_A0 * b / a) * random.uniform(1.2, 2.5), 2)
 
 
 
150
  F_C0 = round(random.choice([0.0, random.uniform(5.0, 20.0)]), 2)
151
  F_D0 = 0.0
152
 
@@ -155,7 +182,7 @@ def template_flow_system_molar_flow_rates():
155
 
156
  # 2. Core Calculations
157
 
158
- # Calculate Theta values for use in the solution string
159
  Theta_B = F_B0 / F_A0
160
  Theta_C = F_C0 / F_A0
161
 
@@ -163,75 +190,81 @@ def template_flow_system_molar_flow_rates():
163
  F_A = F_A0 * (1 - X_A)
164
  F_B = F_B0 - (b / a) * F_A0 * X_A
165
  F_C = F_C0 + (c / a) * F_A0 * X_A
166
- F_D = F_D0 + (d / a) * F_A0 * X_A
167
 
168
  # 3. Generate Question and Solution Strings
169
-
170
- def format_species(coeff, name):
171
- return f"{coeff if coeff > 1 else ''}{' ' if coeff > 1 else ''}{name}"
172
-
173
- reaction_string = (
174
- f"{format_species(a, reactant_A_name)} + {format_species(b, reactant_B_name)} -> "
175
- f"{format_species(c, product_C_name)} + {format_species(d, product_D_name)}"
176
- )
177
-
178
  question = (
179
  f"A reaction is carried out in a steady-state Plug Flow Reactor (PFR):\n"
180
- f"**Reaction:** ${reaction_string}$\n\n"
181
- f"The reactor is fed with {reactant_A_name} at a rate of ${F_A0}$ mol/min, "
182
- f"{reactant_B_name} at ${F_B0}$ mol/min, and {product_C_name} at ${F_C0}$ mol/min. "
183
  f"{reactant_A_name} is the limiting reactant.\n\n"
184
- f"The reactor is designed to achieve a conversion of ${X_A}$ ($X_A$) for the limiting reactant. "
185
  f"Calculate the molar flow rates of all species exiting the reactor."
186
  )
187
 
188
  solution = (
189
  f"**Step 1:** Identify Given Information\n"
190
- f"- Reaction: ${reaction_string}$\n"
191
  f"- Inlet Molar Flow Rates:\n"
192
- f" - $F_{{A,0}} = {F_A0}$ mol/min\n"
193
- f" - $F_{{B,0}} = {F_B0}$ mol/min\n"
194
- f" - $F_{{C,0}} = {F_C0}$ mol/min\n"
195
- f" - $F_{{D,0}} = {F_D0}$ mol/min\n"
196
- f"- Conversion of {reactant_A_name}: $X_A = {X_A}$\n\n"
 
 
 
 
197
 
198
  f"**Step 2:** Stoichiometric Relations for a Flow System\n"
199
- f"The outlet molar flow rate ($F_j$) of any species is given by:\n"
200
- f"$F_j = F_{{j,0}} + \\nu_j F_{{A,0}} X_A$\n"
201
- f"Alternatively, in terms of $\\Theta_j = F_{{j,0}} / F_{{A,0}}$:\n"
202
- f"$F_j = F_{{A,0}}(\\Theta_j + \\nu_j X_A)$\n"
203
- f"Here, the normalized stoichiometric coefficients ($\\nu_j$) are:\n"
204
- f"$\\nu_A = -1$, $\\nu_B = -{b}/{a}$, $\\nu_C = +{c}/{a}$, $\\nu_D = +{d}/{a}$\n\n"
205
-
 
 
 
 
 
206
  f"**Step 3:** Calculate Outlet Molar Flow Rates\n\n"
207
  f"**For {reactant_A_name} (A):**\n"
208
- f"$F_A = F_{{A,0}}(1 - X_A) = {F_A0}(1 - {X_A}) = {round(F_A, 2)}$ mol/min\n\n"
209
 
210
  f"**For {reactant_B_name} (B):**\n"
211
  f"*Using the Direct Method:*\n"
212
- f"$F_B = F_{{B,0}} - ({b}/{a}) F_{{A,0}} X_A = {F_B0} - ({b}/{a}) \\times {F_A0} \\times {X_A} = {round(F_B, 2)}$ mol/min\n"
213
- f"*Using the $\\Theta$ Method:*\n"
214
- f"First, $\\Theta_B = F_{{B,0}} / F_{{A,0}} = {F_B0} / {F_A0} = {round(Theta_B, 3)}$\n"
215
- f"$F_B = F_{{A,0}}(\\Theta_B - ({b}/{a})X_A) = {F_A0}({round(Theta_B, 3)} - ({b}/{a}) \\times {X_A}) = {round(F_B, 2)}$ mol/min\n\n"
216
 
217
  f"**For {product_C_name} (C):**\n"
218
  f"*Using the Direct Method:*\n"
219
- f"$F_C = F_{{C,0}} + ({c}/{a}) F_{{A,0}} X_A = {F_C0} + ({c}/{a}) \\times {F_A0} \\times {X_A} = {round(F_C, 2)}$ mol/min\n"
220
- f"*Using the $\\Theta$ Method:*\n"
221
- f"First, $\\Theta_C = F_{{C,0}} / F_{{A,0}} = {F_C0} / {F_A0} = {round(Theta_C, 3)}$\n"
222
- f"$F_C = F_{{A,0}}(\\Theta_C + ({c}/{a})X_A) = {F_A0}({round(Theta_C, 3)} + ({c}/{a}) \\times {X_A}) = {round(F_C, 2)}$ mol/min\n\n"
 
223
 
224
- f"**For {product_D_name} (D):**\n"
225
- f"Since $F_{{D,0}} = 0$, $\\Theta_D = 0$. The calculation is straightforward:\n"
226
- f"$F_D = F_{{D,0}} + ({d}/{a}) F_{{A,0}} X_A = 0 + ({d}/{a}) \\times {F_A0} \\times {X_A} = {round(F_D, 2)}$ mol/min\n\n"
 
 
 
227
 
 
228
  f"**Final Answer**\n"
229
  f"The molar flow rates exiting the reactor are:\n"
230
- f"- {reactant_A_name} ($F_A$): ${round(F_A, 2)}$ mol/min\n"
231
- f"- {reactant_B_name} ($F_B$): ${round(F_B, 2)}$ mol/min\n"
232
- f"- {product_C_name} ($F_C$): ${round(F_C, 2)}$ mol/min\n"
233
- f"- {product_D_name} ($F_D$): ${round(F_D, 2)}$ mol/min"
234
  )
 
 
235
 
236
  return question, solution
237
 
@@ -258,31 +291,44 @@ def template_limiting_reactant():
258
  - str: A question requiring identification of the limiting reactant.
259
  - str: A detailed solution showing the identification and calculation steps.
260
  """
261
- # 1. Randomized Parameters
262
-
263
- # Select unique names for reactants and products
264
- reactant_A_name, reactant_B_name = random.sample(GENERAL_REACTANTS, 2)
265
- product_C_name, product_D_name = random.sample(PRODUCTS, 2)
266
-
267
- # Generate stoichiometric coefficients
268
- a = random.randint(1, 3)
269
- b = random.randint(1, 3)
270
- c = random.randint(1, 3)
271
- d = random.randint(1, 3)
 
 
 
 
 
 
 
 
 
 
272
 
273
- # Randomly decide which reactant will be limiting
274
  is_A_limiting = random.choice([True, False])
275
 
276
  # Generate initial moles based on which reactant is limiting
277
  if is_A_limiting:
278
  N_A0 = round(random.uniform(20.0, 50.0), 2)
279
- # Make B the excess reactant
280
- N_B0 = round((N_A0 * b / a) * random.uniform(1.1, 2.0), 2)
 
281
  else: # B is limiting
282
  N_B0 = round(random.uniform(20.0, 50.0), 2)
283
- # Make A the excess reactant
284
- N_A0 = round((N_B0 * a / b) * random.uniform(1.1, 2.0), 2)
 
285
 
 
286
  N_C0 = 0.0
287
  N_D0 = 0.0
288
 
@@ -299,7 +345,6 @@ def template_limiting_reactant():
299
  # Case 1: A is the limiting reactant
300
  limiting_reactant_name = reactant_A_name
301
  limiting_reactant_sym = 'A'
302
- excess_reactant_name = reactant_B_name
303
 
304
  # Calculate final moles based on X_A
305
  N_A = N_A0 * (1 - X)
@@ -310,7 +355,6 @@ def template_limiting_reactant():
310
  # Case 2: B is the limiting reactant
311
  limiting_reactant_name = reactant_B_name
312
  limiting_reactant_sym = 'B'
313
- excess_reactant_name = reactant_A_name
314
 
315
  # Calculate final moles based on X_B
316
  N_A = N_A0 - (a / b) * N_B0 * X
@@ -319,20 +363,14 @@ def template_limiting_reactant():
319
  N_D = N_D0 + (d / b) * N_B0 * X
320
 
321
  # 3. Generate Question and Solution Strings
322
- def format_species(coeff, name):
323
- return f"{coeff if coeff > 1 else ''}{' ' if coeff > 1 else ''}{name}"
324
-
325
- reaction_string = (
326
- f"{format_species(a, reactant_A_name)} + {format_species(b, reactant_B_name)} -> "
327
- f"{format_species(c, product_C_name)} + {format_species(d, product_D_name)}"
328
- )
329
 
330
  question = (
331
  f"The following reaction occurs in a batch reactor:\n"
332
- f"**Reaction:** ${reaction_string}$\n\n"
333
- f"The reactor is initially charged with ${N_A0}$ moles of {reactant_A_name} and "
334
- f"${N_B0}$ moles of {reactant_B_name}. The reaction is allowed to proceed until "
335
- f"a ${X*100}\%$ conversion of the limiting reactant is achieved.\n\n"
336
  f"First, identify the limiting reactant. Then, calculate the final number of moles for all species."
337
  )
338
 
@@ -340,55 +378,44 @@ def template_limiting_reactant():
340
  f"**Step 1:** Identify the Limiting Reactant\n"
341
  f"To find the limiting reactant, we compare the ratio of the initial moles to the "
342
  f"stoichiometric coefficient for each reactant.\n\n"
343
- f"- For **{reactant_A_name} (A)**: $N_{{A0}}/a = {N_A0} / {a} = \\mathbf{{{round(ratio_A, 2)}}}$\n"
344
- f"- For **{reactant_B_name} (B)**: $N_{{B0}}/b = {N_B0} / {b} = \\mathbf{{{round(ratio_B, 2)}}}$\n\n"
345
- f"Since ${round(min(ratio_A, ratio_B), 2)} < {round(max(ratio_A, ratio_B), 2)}$, "
346
- f"**{limiting_reactant_name} is the limiting reactant**. All further calculations will be based on "
347
- f"a conversion of {limiting_reactant_name}, $X_{limiting_reactant_sym} = {X}$.\n\n"
348
- )
349
-
350
- solution_step2_calculation = (
351
- f"**Step 2:** Calculate Final Moles\n"
352
- f"Using the stoichiometric relationships based on the limiting reactant ({limiting_reactant_name}):\n\n"
353
- f"**For {reactant_A_name} (A):**\n"
354
- f"$N_A = {round(N_A, 2)}$ mol\n\n"
355
- f"**For {reactant_B_name} (B):**\n"
356
- f"$N_B = {round(N_B, 2)}$ mol\n\n"
357
- f"**For {product_C_name} (C):**\n"
358
- f"$N_C = {round(N_C, 2)}$ mol\n\n"
359
- f"**For {product_D_name} (D):**\n"
360
- f"$N_D = {round(N_D, 2)}$ mol"
361
  )
362
 
363
- solution_final_answer = (
364
- f"**Final Answer**\n"
365
- f"The limiting reactant is **{limiting_reactant_name}**. The final number of moles are:\n"
366
- f"- **{reactant_A_name}:** ${round(N_A, 2)}$ mol\n"
367
- f"- **{reactant_B_name}:** ${round(N_B, 2)}$ mol\n"
368
- f"- **{product_C_name}:** ${round(N_C, 2)}$ mol\n"
369
- f"- **{product_D_name}:** ${round(N_D, 2)}$ mol"
370
- )
371
-
372
  if limiting_reactant_sym == 'A':
373
  solution_step2_calculation = (
374
- f"\n\n**2. Calculate Final Moles**\n"
375
- f"Using $X_A = {X}$ as the basis:\n\n"
376
- f"$N_A = N_{{A0}}(1 - X_A) = {N_A0}(1 - {X}) = \\mathbf{{{round(N_A, 2)}}}$ mol\n"
377
- f"$N_B = N_{{B0}} - (b/a)N_{{A0}}X_A = {N_B0} - ({b}/{a})({N_A0})({X}) = \\mathbf{{{round(N_B, 2)}}}$ mol\n"
378
- f"$N_C = N_{{C0}} + (c/a)N_{{A0}}X_A = {N_C0} + ({c}/{a})({N_A0})({X}) = \\mathbf{{{round(N_C, 2)}}}$ mol\n"
379
- f"$N_D = N_{{D0}} + (d/a)N_{{A0}}X_A = {N_D0} + ({d}/{a})({N_A0})({X}) = \\mathbf{{{round(N_D, 2)}}}$ mol"
380
  )
 
 
381
  else:
382
  solution_step2_calculation = (
383
- f"\n\n**2. Calculate Final Moles**\n"
384
- f"Using $X_B = {X}$ as the basis:\n\n"
385
- f"$N_A = N_{{A0}} - (a/b)N_{{B0}}X_B = {N_A0} - ({a}/{b})({N_B0})({X}) = \\mathbf{{{round(N_A, 2)}}}$ mol\n"
386
- f"$N_B = N_{{B0}}(1 - X_B) = {N_B0}(1 - {X}) = \\mathbf{{{round(N_B, 2)}}}$ mol\n"
387
- f"$N_C = N_{{C0}} + (c/b)N_{{B0}}X_B = {N_C0} + ({c}/{b})({N_B0})({X}) = \\mathbf{{{round(N_C, 2)}}}$ mol\n"
388
- f"$N_D = N_{{D0}} + (d/b)N_{{B0}}X_B = {N_D0} + ({d}/{b})({N_B0})({X}) = \\mathbf{{{round(N_D, 2)}}}$ mol"
389
  )
 
 
 
 
 
 
 
 
 
 
 
 
390
 
391
- solution = f"### **Solution**\n\n{solution_step1_identification}{solution_step2_calculation}{solution_final_answer}"
392
 
393
  return question, solution
394
 
 
1
  import random
2
+ from data.templates.branches.chemical_engineering.constants import GENERAL_REACTANTS, LIQUID_PHASE_REACTANTS, GAS_PHASE_REACTANTS, PRODUCTS, REACTIONS
3
 
4
 
5
  # Template 1 (Easy)
 
23
  - str: A question about calculating final moles in a batch reactor.
24
  - str: A detailed, step-by-step solution.
25
  """
26
+ # 1. Parameterize Inputs using Valid Reactions
27
+ # Select a real, pre-balanced reaction to ensure chemical plausibility
28
+ reaction_data = random.choice(REACTIONS)
29
+ equation = reaction_data["equation"]
30
+
31
+ # Extract reactants and products
32
+ reactant_items = list(reaction_data["reactants"].items())
33
+ product_items = list(reaction_data["products"].items())
34
+
35
+ # Map to A, B, C, D variables
36
+ # We assume the reaction has at least 2 reactants.
37
+ reactant_A_name, a = reactant_items[0]
38
+ reactant_B_name, b = reactant_items[1]
39
 
40
+ product_C_name, c = product_items[0]
41
+ # Handle case where there might be only 1 product
42
+ has_product_D = len(product_items) > 1
43
+ if has_product_D:
44
+ product_D_name, d = product_items[1]
45
+ else:
46
+ product_D_name, d = "None", 0
47
 
48
+ # Generate initial moles
49
+ # Ensure A is the limiting reactant (N_A0/a < N_B0/b)
50
  N_A0 = round(random.uniform(10.0, 25.0), 2)
51
+ min_N_B0 = (N_A0 / a) * b
52
+ N_B0 = round(min_N_B0 * random.uniform(1.2, 2.0), 2)
53
+
54
+ # Explicitly set initial product moles to zero for clarity
55
+ N_C0 = 0.0
56
+ N_D0 = 0.0
57
 
58
  # Generate a realistic conversion for the limiting reactant A
59
  X_A = round(random.uniform(0.40, 0.95), 2)
 
62
  N_A = N_A0 * (1 - X_A)
63
  N_B = N_B0 - (b / a) * N_A0 * X_A
64
  N_C = N_C0 + (c / a) * N_A0 * X_A
65
+ N_D = N_D0 + (d / a) * N_A0 * X_A if has_product_D else 0.0
66
 
67
  # 3. Generate Question and Solution Strings
 
 
 
 
 
 
 
 
 
 
68
  question = (
69
+ f"Consider the following reaction carried out in a batch reactor:\n"
70
+ f"**Reaction:** ${equation}$\n\n"
71
+ f"The reactor is initially charged with ${N_A0}$ moles of {reactant_A_name} and "
72
+ f"${N_B0}$ moles of {reactant_B_name}. Assume the initial amount of products is zero.\n"
73
  f"{reactant_A_name} is the limiting reactant.\n\n"
74
+ f"If the reaction is allowed to proceed until a conversion of ${X_A}$ ($X_A$) is achieved, "
75
  f"calculate the final number of moles of all species in the reactor."
76
  )
77
 
78
  solution = (
79
  f"**Step 1:** Identify Given Information\n"
80
+ f"- Reaction: ${equation}$\n"
81
  f"- Initial Moles:\n"
82
  f" - $N_{{{reactant_A_name},0}} = {N_A0}$ mol\n"
83
  f" - $N_{{{reactant_B_name},0}} = {N_B0}$ mol\n"
84
+ f" - Products = 0 mol\n"
 
85
  f"- Conversion of {reactant_A_name}: $X_A = {X_A}$\n\n"
86
 
87
  f"**Step 2:** Write Stoichiometric Relations in Terms of Conversion\n"
88
+ f"The number of moles of each species ($N_j$) at any conversion $X_A$ is given by:\n"
89
  f"- $N_A = N_{{A,0}}(1 - X_A)$\n"
90
+ f"- $N_B = N_{{B,0}} - ({b}/{a}) N_{{A,0}} X_A$\n"
91
+ f"- $N_C = N_{{C,0}} + ({c}/{a}) N_{{A,0}} X_A$\n"
92
+ )
93
+ if has_product_D:
94
+ solution += f"- $N_D = N_{{D,0}} + ({d}/{a}) N_{{A,0}} X_A$\n"
95
+ solution += "\n"
96
 
97
+ solution += (
98
  f"**Step 3:** Calculate Final Moles for Each Species\n"
99
  f"For {reactant_A_name} (A):\n"
100
+ f"$N_A = {N_A0}(1 - {X_A}) = {N_A0}({1-X_A:.2f}) = {round(N_A, 3)}$ mol\n\n"
101
  f"For {reactant_B_name} (B):\n"
102
  f"$N_B = {N_B0} - ({b}/{a}) \\times {N_A0} \\times {X_A} = {N_B0} - {round((b/a)*N_A0*X_A, 3)} = {round(N_B, 3)}$ mol\n\n"
103
  f"For {product_C_name} (C):\n"
104
+ f"$N_C = 0 + ({c}/{a}) \\times {N_A0} \\times {X_A} = {round((c/a)*N_A0*X_A, 3)} = {round(N_C, 3)}$ mol\n\n"
105
+ )
106
+
107
+ if has_product_D:
108
+ solution += (
109
+ f"For {product_D_name} (D):\n"
110
+ f"$N_D = 0 + ({d}/{a}) \\times {N_A0} \\times {X_A} = {round((d/a)*N_A0*X_A, 3)} = {round(N_D, 3)}$ mol\n\n"
111
+ )
112
 
113
+ solution += (
114
  f"**Final Answer**\n"
115
  f"After reaching a conversion of ${X_A*100} \%$, the final number of moles in the reactor are:\n"
116
  f"- {reactant_A_name}: ${round(N_A, 3)}$ mol\n"
117
  f"- {reactant_B_name}: ${round(N_B, 3)}$ mol\n"
118
  f"- {product_C_name}: ${round(N_C, 3)}$ mol\n"
 
119
  )
120
+ if has_product_D:
121
+ solution += f"- {product_D_name}: ${round(N_D, 3)}$ mol"
122
 
123
  return question, solution
124
 
 
134
  inlet flow rates and the conversion of a limiting reactant. The calculation
135
  is an application of stoichiometric principles to a flow system, using:
136
 
137
+ F_A = F_A0(1 - X_A)
138
+ F_j = F_j0 + nu_j * F_A0 * X_A = F_A0(Theta_j + nu_j * X_A)
139
 
140
+ where nu_j is the stoichiometric coefficient and Theta_j = F_j0/F_A0.
141
 
142
  Returns:
143
  tuple: A tuple containing:
144
  - str: A question about calculating outlet molar flow rates.
145
  - str: A detailed, step-by-step solution.
146
  """
147
+ # 1. Parameterize Inputs using Valid Reactions
148
+ # Select a real, pre-balanced reaction to ensure chemical plausibility
149
+ reaction_data = random.choice(REACTIONS)
150
+ equation = reaction_data["equation"]
151
+
152
+ # Extract reactants and products
153
+ # The dictionaries are in the format {"Name": coefficient}
154
+ reactant_items = list(reaction_data["reactants"].items())
155
+ product_items = list(reaction_data["products"].items())
156
+
157
+ # Map to A, B, C, D variables
158
+ # We assume the reaction has at least 2 reactants.
159
+ reactant_A_name, a = reactant_items[0]
160
+ reactant_B_name, b = reactant_items[1]
161
 
162
+ product_C_name, c = product_items[0]
163
+ # Handle case where there might be only 1 product
164
+ has_product_D = len(product_items) > 1
165
+ if has_product_D:
166
+ product_D_name, d = product_items[1]
167
+ else:
168
+ product_D_name, d = "None", 0
 
 
169
 
170
+ # Generate inlet molar flow rates (mol/min)
171
+ # Ensure A is the limiting reactant by providing excess B
172
  F_A0 = round(random.uniform(50.0, 150.0), 2)
173
+ min_F_B0 = (F_A0 / a) * b
174
+ F_B0 = round(min_F_B0 * random.uniform(1.2, 2.5), 2)
175
+
176
+ # Inlet product flow is usually zero or small
177
  F_C0 = round(random.choice([0.0, random.uniform(5.0, 20.0)]), 2)
178
  F_D0 = 0.0
179
 
 
182
 
183
  # 2. Core Calculations
184
 
185
+ # Calculate Theta values
186
  Theta_B = F_B0 / F_A0
187
  Theta_C = F_C0 / F_A0
188
 
 
190
  F_A = F_A0 * (1 - X_A)
191
  F_B = F_B0 - (b / a) * F_A0 * X_A
192
  F_C = F_C0 + (c / a) * F_A0 * X_A
193
+ F_D = F_D0 + (d / a) * F_A0 * X_A if has_product_D else 0.0
194
 
195
  # 3. Generate Question and Solution Strings
 
 
 
 
 
 
 
 
 
196
  question = (
197
  f"A reaction is carried out in a steady-state Plug Flow Reactor (PFR):\n"
198
+ f"**Reaction:** {equation}\n\n"
199
+ f"The reactor is fed with {reactant_A_name} at a rate of {F_A0} mol/min, "
200
+ f"{reactant_B_name} at {F_B0} mol/min, and {product_C_name} at {F_C0} mol/min. "
201
  f"{reactant_A_name} is the limiting reactant.\n\n"
202
+ f"The reactor is designed to achieve a conversion of {X_A} (X_A) for the limiting reactant. "
203
  f"Calculate the molar flow rates of all species exiting the reactor."
204
  )
205
 
206
  solution = (
207
  f"**Step 1:** Identify Given Information\n"
208
+ f"- Reaction: {equation}\n"
209
  f"- Inlet Molar Flow Rates:\n"
210
+ f" - F_A0 = {F_A0} mol/min\n"
211
+ f" - F_B0 = {F_B0} mol/min\n"
212
+ f" - F_C0 = {F_C0} mol/min\n"
213
+ )
214
+ if has_product_D:
215
+ solution += f" - F_D0 = {F_D0} mol/min\n"
216
+
217
+ solution += (
218
+ f"- Conversion of {reactant_A_name}: X_A = {X_A}\n\n"
219
 
220
  f"**Step 2:** Stoichiometric Relations for a Flow System\n"
221
+ f"The outlet molar flow rate (F_j) of any species is given by:\n"
222
+ f"F_j = F_j0 + nu_j * F_A0 * X_A\n"
223
+ f"Alternatively, in terms of Theta_j = F_j0 / F_A0:\n"
224
+ f"F_j = F_A0(Theta_j + nu_j * X_A)\n"
225
+ f"Here, the normalized stoichiometric coefficients (nu_j) relative to A are:\n"
226
+ f"nu_A = -1, nu_B = -{b}/{a}, nu_C = +{c}/{a}"
227
+ )
228
+ if has_product_D:
229
+ solution += f", nu_D = +{d}/{a}"
230
+ solution += "\n\n"
231
+
232
+ solution += (
233
  f"**Step 3:** Calculate Outlet Molar Flow Rates\n\n"
234
  f"**For {reactant_A_name} (A):**\n"
235
+ f"F_A = F_A0(1 - X_A) = {F_A0}(1 - {X_A}) = {round(F_A, 2)} mol/min\n\n"
236
 
237
  f"**For {reactant_B_name} (B):**\n"
238
  f"*Using the Direct Method:*\n"
239
+ f"F_B = F_B0 - ({b}/{a}) * F_A0 * X_A = {F_B0} - ({b}/{a}) * {F_A0} * {X_A} = {round(F_B, 2)} mol/min\n"
240
+ f"*Using the Theta Method:*\n"
241
+ f"Theta_B = F_B0 / F_A0 = {F_B0} / {F_A0} = {round(Theta_B, 3)}\n"
242
+ f"F_B = F_A0(Theta_B - ({b}/{a})X_A) = {F_A0}({round(Theta_B, 3)} - ({b}/{a}) * {X_A}) = {round(F_B, 2)} mol/min\n\n"
243
 
244
  f"**For {product_C_name} (C):**\n"
245
  f"*Using the Direct Method:*\n"
246
+ f"F_C = F_C0 + ({c}/{a}) * F_A0 * X_A = {F_C0} + ({c}/{a}) * {F_A0} * {X_A} = {round(F_C, 2)} mol/min\n"
247
+ f"*Using the Theta Method:*\n"
248
+ f"Theta_C = F_C0 / F_A0 = {F_C0} / {F_A0} = {round(Theta_C, 3)}\n"
249
+ f"F_C = F_A0(Theta_C + ({c}/{a})X_A) = {F_A0}({round(Theta_C, 3)} + ({c}/{a}) * {X_A}) = {round(F_C, 2)} mol/min\n\n"
250
+ )
251
 
252
+ if has_product_D:
253
+ solution += (
254
+ f"**For {product_D_name} (D):**\n"
255
+ f"Since F_D0 = 0, the calculation is straightforward:\n"
256
+ f"F_D = F_D0 + ({d}/{a}) * F_A0 * X_A = 0 + ({d}/{a}) * {F_A0} * {X_A} = {round(F_D, 2)} mol/min\n\n"
257
+ )
258
 
259
+ solution += (
260
  f"**Final Answer**\n"
261
  f"The molar flow rates exiting the reactor are:\n"
262
+ f"- {reactant_A_name} (F_A): {round(F_A, 2)} mol/min\n"
263
+ f"- {reactant_B_name} (F_B): {round(F_B, 2)} mol/min\n"
264
+ f"- {product_C_name} (F_C): {round(F_C, 2)} mol/min\n"
 
265
  )
266
+ if has_product_D:
267
+ solution += f"- {product_D_name} (F_D): {round(F_D, 2)} mol/min"
268
 
269
  return question, solution
270
 
 
291
  - str: A question requiring identification of the limiting reactant.
292
  - str: A detailed solution showing the identification and calculation steps.
293
  """
294
+ # 1. Parameterize Inputs using Valid Reactions
295
+ # Select a real, pre-balanced reaction to ensure chemical plausibility
296
+ reaction_data = random.choice(REACTIONS)
297
+
298
+ # Extract reactants and products
299
+ # The dictionaries are in the format {"Name": coefficient}
300
+ reactant_items = list(reaction_data["reactants"].items())
301
+ product_items = list(reaction_data["products"].items())
302
+
303
+ # Map to A, B, C, D variables for the template logic
304
+ # We assume the reaction has at least 2 reactants and 1-2 products based on the constants file
305
+ reactant_A_name, a = reactant_items[0]
306
+ reactant_B_name, b = reactant_items[1]
307
+
308
+ product_C_name, c = product_items[0]
309
+ # Handle case where there might be only 1 product or 2
310
+ if len(product_items) > 1:
311
+ product_D_name, d = product_items[1]
312
+ else:
313
+ # Create a dummy placeholder if only 1 product exists to prevent errors
314
+ product_D_name, d = "Heat/Other", 0
315
 
316
+ # Randomly decide which reactant will be limiting to vary the problem type
317
  is_A_limiting = random.choice([True, False])
318
 
319
  # Generate initial moles based on which reactant is limiting
320
  if is_A_limiting:
321
  N_A0 = round(random.uniform(20.0, 50.0), 2)
322
+ # Make B the excess reactant (ratio N_B0/b > N_A0/a)
323
+ min_B = (N_A0 / a) * b
324
+ N_B0 = round(min_B * random.uniform(1.2, 2.0), 2)
325
  else: # B is limiting
326
  N_B0 = round(random.uniform(20.0, 50.0), 2)
327
+ # Make A the excess reactant (ratio N_A0/a > N_B0/b)
328
+ min_A = (N_B0 / b) * a
329
+ N_A0 = round(min_A * random.uniform(1.2, 2.0), 2)
330
 
331
+ # Explicitly define initial product moles as zero
332
  N_C0 = 0.0
333
  N_D0 = 0.0
334
 
 
345
  # Case 1: A is the limiting reactant
346
  limiting_reactant_name = reactant_A_name
347
  limiting_reactant_sym = 'A'
 
348
 
349
  # Calculate final moles based on X_A
350
  N_A = N_A0 * (1 - X)
 
355
  # Case 2: B is the limiting reactant
356
  limiting_reactant_name = reactant_B_name
357
  limiting_reactant_sym = 'B'
 
358
 
359
  # Calculate final moles based on X_B
360
  N_A = N_A0 - (a / b) * N_B0 * X
 
363
  N_D = N_D0 + (d / b) * N_B0 * X
364
 
365
  # 3. Generate Question and Solution Strings
366
+ equation = reaction_data["equation"]
 
 
 
 
 
 
367
 
368
  question = (
369
  f"The following reaction occurs in a batch reactor:\n"
370
+ f"**Reaction:** {equation}\n\n"
371
+ f"The reactor is initially charged with {N_A0} moles of {reactant_A_name} and "
372
+ f"{N_B0} moles of {reactant_B_name}. Assume the initial amount of products is zero.\n"
373
+ f"The reaction is allowed to proceed until a {X*100}% conversion of the limiting reactant is achieved.\n\n"
374
  f"First, identify the limiting reactant. Then, calculate the final number of moles for all species."
375
  )
376
 
 
378
  f"**Step 1:** Identify the Limiting Reactant\n"
379
  f"To find the limiting reactant, we compare the ratio of the initial moles to the "
380
  f"stoichiometric coefficient for each reactant.\n\n"
381
+ f"- For **{reactant_A_name}**: N_A0/a = {N_A0} / {a} = **{round(ratio_A, 2)}**\n"
382
+ f"- For **{reactant_B_name}**: N_B0/b = {N_B0} / {b} = **{round(ratio_B, 2)}**\n\n"
383
+ f"Since {round(min(ratio_A, ratio_B), 2)} < {round(max(ratio_A, ratio_B), 2)}, "
384
+ f"**{limiting_reactant_name} is the limiting reactant**.\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  )
386
 
 
 
 
 
 
 
 
 
 
387
  if limiting_reactant_sym == 'A':
388
  solution_step2_calculation = (
389
+ f"\n**Step 2:** Calculate Final Moles\n"
390
+ f"Using conversion of {reactant_A_name} (X = {X}) as the basis:\n\n"
391
+ f"Moles {reactant_A_name} = {N_A0}(1 - {X}) = **{round(N_A, 2)}** mol\n"
392
+ f"Moles {reactant_B_name} = {N_B0} - ({b}/{a})({N_A0})({X}) = **{round(N_B, 2)}** mol\n"
393
+ f"Moles {product_C_name} = 0 + ({c}/{a})({N_A0})({X}) = **{round(N_C, 2)}** mol\n"
 
394
  )
395
+ if d > 0:
396
+ solution_step2_calculation += f"Moles {product_D_name} = 0 + ({d}/{a})({N_A0})({X}) = **{round(N_D, 2)}** mol\n"
397
  else:
398
  solution_step2_calculation = (
399
+ f"\n**Step 2:** Calculate Final Moles\n"
400
+ f"Using conversion of {reactant_B_name} (X = {X}) as the basis:\n\n"
401
+ f"Moles {reactant_A_name} = {N_A0} - ({a}/{b})({N_B0})({X}) = **{round(N_A, 2)}** mol\n"
402
+ f"Moles {reactant_B_name} = {N_B0}(1 - {X}) = **{round(N_B, 2)}** mol\n"
403
+ f"Moles {product_C_name} = 0 + ({c}/{b})({N_B0})({X}) = **{round(N_C, 2)}** mol\n"
 
404
  )
405
+ if d > 0:
406
+ solution_step2_calculation += f"Moles {product_D_name} = 0 + ({d}/{b})({N_B0})({X}) = **{round(N_D, 2)}** mol\n"
407
+
408
+ solution_final_answer = (
409
+ f"\n**Final Answer**\n"
410
+ f"The limiting reactant is **{limiting_reactant_name}**. The final number of moles are:\n"
411
+ f"- **{reactant_A_name}:** {round(N_A, 2)} mol\n"
412
+ f"- **{reactant_B_name}:** {round(N_B, 2)} mol\n"
413
+ f"- **{product_C_name}:** {round(N_C, 2)} mol\n"
414
+ )
415
+ if d > 0:
416
+ solution_final_answer += f"- **{product_D_name}:** {round(N_D, 2)} mol"
417
 
418
+ solution = f"{solution_step1_identification}{solution_step2_calculation}{solution_final_answer}"
419
 
420
  return question, solution
421
 
data/templates/branches/chemical_engineering/thermodynamics/__pycache__/heat_effects.cpython-311.pyc ADDED
Binary file (25 kB). View file
 
data/templates/branches/chemical_engineering/thermodynamics/__pycache__/volumetric_properties_pure_fluids.cpython-311.pyc ADDED
Binary file (35.4 kB). View file
 
data/templates/branches/chemical_engineering/thermodynamics/heat_effects.py CHANGED
@@ -16,11 +16,6 @@ def template_sensible_heat_constant_cp():
16
 
17
  The governing equation on a mass basis is:
18
  Q = m * Cp * (T2 - T1)
19
- Where:
20
- - Q: Total heat transferred
21
- - m: Mass of the substance
22
- - Cp: Constant pressure specific heat capacity
23
- - T1, T2: Initial and final temperatures
24
 
25
  Returns:
26
  tuple: A tuple containing:
@@ -31,14 +26,23 @@ def template_sensible_heat_constant_cp():
31
  substance_data = random.choice(SUBSTANCES_FOR_HEATING)
32
  substance_name = substance_data["name"]
33
  substance_state = substance_data["state"]
34
- Cp = substance_data["Cp"] # J/(g·K) - Specific heat, not molar
 
 
 
 
 
35
 
36
  # Generate a random mass in grams
37
- m = round(random.uniform(50.0, 1000.0), 1) # grams
38
 
39
- # Generate temperatures in Celsius
40
- T1_C = round(random.uniform(10.0, 50.0), 1)
41
- T2_C = round(random.uniform(T1_C + 30, 150.0), 1)
 
 
 
 
42
 
43
  # 2. Perform the core calculation
44
  delta_T = T2_C - T1_C
@@ -51,13 +55,15 @@ def template_sensible_heat_constant_cp():
51
  question = (
52
  f"Calculate the heat in kJ required to raise the temperature of {m} g "
53
  f"of {substance_state} {substance_name} from {T1_C}°C to {T2_C}°C. "
54
- f"The specific heat capacity of {substance_name} is {Cp} J/g·K."
 
55
  )
56
 
57
  solution = (
58
  f"**Step 1:** State the formula.\n"
59
  f"Q = m * Cp * ΔT\n\n"
60
- f"**Note:** We assume the specific heat capacity (Cp) is constant over this temperature range."
 
61
 
62
  f"**Step 2:** List the given values.\n"
63
  f"- Mass (m) = {m} g\n"
@@ -248,8 +254,16 @@ def template_sensible_heat_temp_dependent_cp():
248
  A, B, C, D = params["A"], params["B"], params["C"], params["D"]
249
 
250
  n = round(random.uniform(1.0, 10.0), 2) # moles
251
- T1 = round(random.uniform(298.15, 400.0), 2) # Kelvin
252
- T2 = round(random.uniform(T1 + 200, 900.0), 2) # Kelvin
 
 
 
 
 
 
 
 
253
 
254
  # 2. Perform the core calculation via analytical integration
255
  # integral(A + BT + CT² + DT⁻²)dT = AT + (B/2)T² + (C/3)T³ - D/T
@@ -261,17 +275,21 @@ def template_sensible_heat_temp_dependent_cp():
261
  return term_a + term_b + term_c + term_d
262
 
263
  # Calculate the definite integral per mole
264
- integral_val = R * (integral_mean_cp_over_r(T2) - integral_mean_cp_over_r(T1))
 
 
265
 
266
  Q_J = n * integral_val # Total heat in Joules
267
  Q_kJ = Q_J / 1000 # Convert to kilojoules
268
 
269
  # 3. Generate the question and solution strings
270
  # Dynamically create the Cp/R equation string for the question
271
- cp_eq_str = f"A"
272
- if B != 0: cp_eq_str += f" + B*T"
273
- if C != 0: cp_eq_str += f" + C*T²"
274
- if D != 0: cp_eq_str += f" + D*T⁻²"
 
 
275
 
276
  question = (
277
  f"Calculate the heat in kJ required to raise the temperature of {n} moles of "
@@ -281,25 +299,34 @@ def template_sensible_heat_temp_dependent_cp():
281
  f"Where the constants are: A={A}, B={B:.3g}, C={C:.3g}, D={D:.3g}"
282
  )
283
 
 
 
 
 
 
 
 
 
284
  solution = (
285
  f"**Step 1:** State the integral formula for total heat (Q).\n"
286
- f"Q = n * ∫[from T1 to T2] Cp(T) dT\n"
287
- f"Given Cp(T) = R * ({cp_eq_str}), the integral is:\n"
288
- f"Q = n * R * ∫[from {T1} to {T2}] ({A} + {B:.3g}*T {'+ ' + str(C) + '*T²' if C != 0 else ''} {'+ ' + str(D) + '*T⁻²' if D != 0 else ''}) dT\n\n"
289
 
290
  f"**Step 2:** Perform the analytical integration.\n"
291
- f"The indefinite integral of Cp(T)/R is: A*T + (B/2)*T² + (C/3)*T³ - D/T\n\n"
 
292
 
293
- f"**Step 3:** Evaluate the definite integral from T1 to T2.\n"
294
- f"∫ Cp(T) dT = R * [ (A*T₂ + (B/2)*T₂² + ...) - (A*T₁ + (B/2)*T₁² + ...) ]\n"
295
- f"∫ Cp(T) dT = {R} J/mol·K * [({integral_mean_cp_over_r(T2):.2f}) - ({integral_mean_cp_over_r(T1):.2f})]\n"
296
- f" Cp(T) dT = {R} J/mol·K * ({(integral_mean_cp_over_r(T2) - integral_mean_cp_over_r(T1)):.2f}) K = {integral_val:.2f} J/mol\n\n"
297
 
298
- f"**Step 4:** Calculate the total heat Q for {n} moles.\n"
299
- f"Q = n * ({integral_val:.2f} J/mol) = {n} mol * {integral_val:.2f} J/mol = {Q_J:.2f} J\n\n"
 
 
300
 
301
- f"**Step 5:** Convert the result to kilojoules.\n"
302
- f"Q = {Q_J:.2f} J * (1 kJ / 1000 J) = {Q_kJ:.2f} kJ\n\n"
303
 
304
  f"**Answer:** The heat required is **{Q_kJ:.2f} kJ**."
305
  )
@@ -311,70 +338,89 @@ def template_sensible_heat_temp_dependent_cp():
311
  def template_adiabatic_flame_temperature():
312
  """
313
  Adiabatic Flame Temperature
314
-
315
- Scenario:
316
- This template calculates the theoretical maximum temperature the products
317
- of a combustion reaction can reach if it occurs adiabatically (no heat loss).
318
- This requires an energy balance: the heat released by the reaction at a
319
- reference temperature (298.15 K) must be completely absorbed by the
320
- product gases as sensible heat, raising their temperature from the
321
- reference temperature to the final adiabatic flame temperature (T_ad).
322
-
323
- Because the heat capacities of the products are temperature-dependent,
324
- this problem requires an iterative, numerical solution.
325
-
326
- Returns:
327
- tuple: A tuple containing:
328
- - str: A question asking to compute the adiabatic flame temperature.
329
- - str: A step-by-step solution showing the numerical method.
330
  """
331
  # 1. Parameterize inputs
332
  R = 8.314 # J/(mol·K)
333
  T_initial = 298.15 # K
334
 
335
- reaction_data = random.choice(COMBUSTION_REACTIONS)
336
- fuel = reaction_data["fuel"]
337
- equation = reaction_data["equation"]
338
- reactants = reaction_data["reactants"]
339
- products = reaction_data["products"]
340
-
341
- # 2. Perform the core calculation
342
- # First, calculate the standard heat of reaction at 298.15 K
343
- try:
344
- products_enthalpy_298 = sum(nu * HEATS_OF_FORMATION[s] for s, nu in products.items())
345
- reactants_enthalpy_298 = sum(nu * HEATS_OF_FORMATION[s] for s, nu in reactants.items())
346
- delta_H_298 = products_enthalpy_298 - reactants_enthalpy_298 # result in kJ
347
- except KeyError as e:
348
- # Handle cases where data might be missing for a defined reaction
349
- return (f"Data Error for {fuel} combustion.", f"Missing heat of formation for {e.args[0]}.")
350
-
351
- # Define a function for the integral of Cp/R for a single species
352
- def integral_mean_cp_over_r(T, A, B, C, D):
353
- return A*T + (B/2)*T**2 + (C/3)*T**3 - D/T
354
-
355
- # Define the function that represents the energy balance equation
356
- # We want to find T_final where this function equals zero.
357
- def energy_balance(T_final):
358
- # Calculate sensible heat absorbed by products from T_initial to T_final
359
- # Units will be J
360
- sensible_heat_products = 0
361
- for species, nu in products.items():
362
- params = CP_PARAMS[species]
363
- A, B, C, D = params["A"], params["B"], params["C"], params["D"]
364
-
365
- # Integral from T_initial to T_final
366
- integral_val = integral_mean_cp_over_r(T_final, A, B, C, D) - integral_mean_cp_over_r(T_initial, A, B, C, D)
367
- sensible_heat_products += nu * R * integral_val
368
-
369
- # Energy Balance: Sensible Heat Absorbed + Heat of Reaction = 0
370
- # Convert heat of reaction from kJ to J for consistency
371
- return sensible_heat_products + (delta_H_298 * 1000)
372
-
373
- # Solve for the root of the energy_balance function
374
- initial_guess = 2000 # A reasonable starting guess in Kelvin
375
- adiabatic_temp_kelvin = fsolve(energy_balance, initial_guess)[0]
376
-
377
- # 3. Generate the question and solution strings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  question = (
379
  f"{fuel} gas enters a furnace at {T_initial} K and is burned completely with "
380
  f"the theoretical amount of dry air (also at {T_initial} K). Assuming the "
@@ -387,20 +433,20 @@ def template_adiabatic_flame_temperature():
387
  f" {equation}\n\n"
388
 
389
  f"**Step 2:** Calculate the standard heat of reaction at {T_initial} K (ΔH_rxn°).\n"
390
- f"Using the provided heats of formation, the calculation is:\n"
391
- f"ΔH_rxn° = Σ(ν·ΔH_f°)products - Σ(ν·ΔH_f°)reactants = {delta_H_298:.2f} kJ\n\n"
392
 
393
  f"**Step 3:** Set up the energy balance equation.\n"
394
  f"For an adiabatic process, the heat released by the reaction must be absorbed as sensible heat by the products.\n"
395
  f"Heat Absorbed by Products + Heat of Reaction = 0\n"
396
- f" Σ[n_i * ∫[from {T_initial} to T_ad] Cp_i(T) dT]_products + ΔH_rxn° = 0\n\n"
397
 
398
  f"**Step 4:** Solve the equation for the adiabatic flame temperature (T_ad).\n"
399
- f"This equation is non-linear because Cp is a function of temperature. We must use a numerical root-finding algorithm to solve for T_ad where the energy balance function equals zero.\n"
400
- f"An initial guess of {initial_guess} K is used for the solver.\n\n"
401
 
402
- f"**Step 5:** State the result from the numerical solver.\n"
403
- f"The solver finds the temperature T_ad that satisfies the energy balance.\n"
404
  f"T_ad = {adiabatic_temp_kelvin:.2f} K\n\n"
405
 
406
  f"**Answer:** The estimated adiabatic flame temperature is **{adiabatic_temp_kelvin:.2f} K**."
 
16
 
17
  The governing equation on a mass basis is:
18
  Q = m * Cp * (T2 - T1)
 
 
 
 
 
19
 
20
  Returns:
21
  tuple: A tuple containing:
 
26
  substance_data = random.choice(SUBSTANCES_FOR_HEATING)
27
  substance_name = substance_data["name"]
28
  substance_state = substance_data["state"]
29
+ Cp = substance_data["Cp"] # J/(g·K)
30
+
31
+ # Retrieve safe temperature limits from the constant data
32
+ # Defaults provided in case keys are missing in legacy data
33
+ min_temp = substance_data.get("min_temp", 20)
34
+ max_temp = substance_data.get("max_temp", 150)
35
 
36
  # Generate a random mass in grams
37
+ m = round(random.uniform(50.0, 1000.0), 1)
38
 
39
+ # Generate temperatures within the safe bounds for this specific substance
40
+ # Ensure there's at least a 10 degree window for T2
41
+ safe_max_T1 = max(min_temp, max_temp - 15)
42
+ T1_C = round(random.uniform(min_temp, safe_max_T1), 1)
43
+
44
+ # Ensure T2 is higher than T1 but within max limit
45
+ T2_C = round(random.uniform(T1_C + 10, max_temp), 1)
46
 
47
  # 2. Perform the core calculation
48
  delta_T = T2_C - T1_C
 
55
  question = (
56
  f"Calculate the heat in kJ required to raise the temperature of {m} g "
57
  f"of {substance_state} {substance_name} from {T1_C}°C to {T2_C}°C. "
58
+ f"The specific heat capacity of {substance_name} is {Cp} J/g·K. "
59
+ f"Assume constant pressure and no phase change occurs."
60
  )
61
 
62
  solution = (
63
  f"**Step 1:** State the formula.\n"
64
  f"Q = m * Cp * ΔT\n\n"
65
+ f"**Note:** We assume the specific heat capacity (Cp) is constant over this temperature range.\n"
66
+ f"\n\n"
67
 
68
  f"**Step 2:** List the given values.\n"
69
  f"- Mass (m) = {m} g\n"
 
254
  A, B, C, D = params["A"], params["B"], params["C"], params["D"]
255
 
256
  n = round(random.uniform(1.0, 10.0), 2) # moles
257
+
258
+ # Determine valid temperature ranges based on phase to ensure physical plausibility
259
+ if "(l)" in substance_name:
260
+ # Liquids: Keep range lower to avoid boiling (approximate general cap)
261
+ T1 = round(random.uniform(280.0, 300.0), 2)
262
+ T2 = round(random.uniform(T1 + 20, 350.0), 2)
263
+ else:
264
+ # Gases/Solids: Can handle higher temperatures
265
+ T1 = round(random.uniform(298.15, 500.0), 2)
266
+ T2 = round(random.uniform(T1 + 100, 1200.0), 2)
267
 
268
  # 2. Perform the core calculation via analytical integration
269
  # integral(A + BT + CT² + DT⁻²)dT = AT + (B/2)T² + (C/3)T³ - D/T
 
275
  return term_a + term_b + term_c + term_d
276
 
277
  # Calculate the definite integral per mole
278
+ val_T2 = integral_mean_cp_over_r(T2)
279
+ val_T1 = integral_mean_cp_over_r(T1)
280
+ integral_val = R * (val_T2 - val_T1)
281
 
282
  Q_J = n * integral_val # Total heat in Joules
283
  Q_kJ = Q_J / 1000 # Convert to kilojoules
284
 
285
  # 3. Generate the question and solution strings
286
  # Dynamically create the Cp/R equation string for the question
287
+ cp_eq_parts = [str(A)]
288
+ if B != 0: cp_eq_parts.append(f"{B:.3e}*T")
289
+ if C != 0: cp_eq_parts.append(f"{C:.3e}*T^2")
290
+ if D != 0: cp_eq_parts.append(f"{D:.3e}*T^-2")
291
+
292
+ cp_eq_str = " + ".join(cp_eq_parts).replace("+ -", "- ")
293
 
294
  question = (
295
  f"Calculate the heat in kJ required to raise the temperature of {n} moles of "
 
299
  f"Where the constants are: A={A}, B={B:.3g}, C={C:.3g}, D={D:.3g}"
300
  )
301
 
302
+ # Helper to format the polynomial substitution string for the solution
303
+ def format_poly_sub(T_val):
304
+ terms = [f"{A}*({T_val})"]
305
+ if B != 0: terms.append(f"({B:.3e}/2)*({T_val})^2")
306
+ if C != 0: terms.append(f"({C:.3e}/3)*({T_val})^3")
307
+ if D != 0: terms.append(f"-({D:.3e}/{T_val})")
308
+ return " + ".join(terms).replace("+ -", "- ")
309
+
310
  solution = (
311
  f"**Step 1:** State the integral formula for total heat (Q).\n"
312
+ f"Q = n * R * ∫[from {T1} to {T2}] (Cp/R) dT\n\n"
 
 
313
 
314
  f"**Step 2:** Perform the analytical integration.\n"
315
+ f"The indefinite integral of Cp(T)/R is:\n"
316
+ f"I(T) = A*T + (B/2)*T^2 + (C/3)*T^3 - D/T\n\n"
317
 
318
+ f"**Step 3:** Evaluate the definite integral.\n"
319
+ f"I({T2}) = {format_poly_sub(T2)} = {val_T2:.4f}\n"
320
+ f"I({T1}) = {format_poly_sub(T1)} = {val_T1:.4f}\n"
321
+ f"Integral Value = I({T2}) - I({T1}) = {val_T2:.4f} - {val_T1:.4f} = {val_T2 - val_T1:.4f} K\n\n"
322
 
323
+ f"**Step 4:** Calculate the total heat Q.\n"
324
+ f"Q = n * R * (Integral Value)\n"
325
+ f"Q = {n} mol * 8.314 J/(mol K) * {val_T2 - val_T1:.4f} K\n"
326
+ f"Q = {Q_J:.2f} J\n\n"
327
 
328
+ f"**Step 5:** Convert to kilojoules.\n"
329
+ f"Q = {Q_J:.2f} J / 1000 = {Q_kJ:.2f} kJ\n\n"
330
 
331
  f"**Answer:** The heat required is **{Q_kJ:.2f} kJ**."
332
  )
 
338
  def template_adiabatic_flame_temperature():
339
  """
340
  Adiabatic Flame Temperature
341
+ Scenario: Calculates the theoretical maximum temperature of combustion products.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  """
343
  # 1. Parameterize inputs
344
  R = 8.314 # J/(mol·K)
345
  T_initial = 298.15 # K
346
 
347
+ # SAFETY MECHANISM: Prevent infinite loops
348
+ max_retries = 20
349
+ attempts = 0
350
+
351
+ while attempts < max_retries:
352
+ attempts += 1
353
+ try:
354
+ # Pick a random reaction
355
+ reaction_data = random.choice(COMBUSTION_REACTIONS)
356
+ fuel = reaction_data["fuel"]
357
+ equation = reaction_data["equation"]
358
+ reactants = reaction_data["reactants"]
359
+ products = reaction_data["products"]
360
+
361
+ # PRE-FLIGHT CHECK
362
+ # Before calculating, verify we actually have data for these species.
363
+ # This prevents the silent KeyError loop.
364
+ missing_heat_data = [s for s in list(reactants.keys()) + list(products.keys()) if s not in HEATS_OF_FORMATION]
365
+ if missing_heat_data:
366
+ # Skip this reaction if we lack enthalpy data
367
+ continue
368
+
369
+ missing_cp_data = [s for s in products.keys() if s not in CP_PARAMS]
370
+ if missing_cp_data:
371
+ # Skip this reaction if we lack heat capacity parameters
372
+ continue
373
+
374
+ # 2. Perform the core calculation
375
+ # Calculate standard heat of reaction at 298.15 K
376
+ products_enthalpy_298 = sum(nu * HEATS_OF_FORMATION[s] for s, nu in products.items())
377
+ reactants_enthalpy_298 = sum(nu * HEATS_OF_FORMATION[s] for s, nu in reactants.items())
378
+ delta_H_298_kJ = products_enthalpy_298 - reactants_enthalpy_298
379
+ delta_H_298_J = delta_H_298_kJ * 1000 # Convert kJ to J
380
+
381
+ # Define integral of Cp/R
382
+ def integral_mean_cp_over_r(T, A, B, C, D):
383
+ return A*T + (B/2)*T**2 + (C/3)*T**3 - D/T
384
+
385
+ # Define the energy balance equation (Sensible Heat + Heat of Rxn = 0)
386
+ def energy_balance(T_final):
387
+ sensible_heat_products = 0
388
+ for species, nu in products.items():
389
+ params = CP_PARAMS[species]
390
+ A, B, C, D = params["A"], params["B"], params["C"], params["D"]
391
+
392
+ # Integral from T_initial to T_final
393
+ val_T = integral_mean_cp_over_r(T_final, A, B, C, D)
394
+ val_T0 = integral_mean_cp_over_r(T_initial, A, B, C, D)
395
+
396
+ sensible_heat_products += nu * R * (val_T - val_T0)
397
+
398
+ return sensible_heat_products + delta_H_298_J
399
+
400
+ # Solve for the root
401
+ initial_guess = 2000
402
+ # fsolve returns a numpy array, we take the first element
403
+ result = fsolve(energy_balance, initial_guess)
404
+ adiabatic_temp_kelvin = result[0]
405
+
406
+ # PHYSICS SANITY CHECK
407
+ # We slightly widen the range to avoid rejecting valid high-temp reactions
408
+ # but keep it sane (e.g. max 4500K for most hydrocarbons in air)
409
+ if 1000 < adiabatic_temp_kelvin < 4500:
410
+ break # Valid result found, exit loop (Success!)
411
+
412
+ except (KeyError, ValueError, IndexError, Exception):
413
+ # If solver fails or unexpected error, try next reaction
414
+ continue
415
+
416
+ # FALLBACK: If we exhausted all retries (e.g., data is missing for ALL reactions)
417
+ else:
418
+ return (
419
+ "Error: Could not generate a valid Adiabatic Flame Temperature problem.",
420
+ "Possible causes: Missing thermochemical data in constants.py or solver convergence issues."
421
+ )
422
+
423
+ # 3. Generate the question and solution strings (Only runs if 'break' was hit)
424
  question = (
425
  f"{fuel} gas enters a furnace at {T_initial} K and is burned completely with "
426
  f"the theoretical amount of dry air (also at {T_initial} K). Assuming the "
 
433
  f" {equation}\n\n"
434
 
435
  f"**Step 2:** Calculate the standard heat of reaction at {T_initial} K (ΔH_rxn°).\n"
436
+ f"Using standard heats of formation:\n"
437
+ f"ΔH_rxn° = {delta_H_298_kJ:.2f} kJ\n\n"
438
 
439
  f"**Step 3:** Set up the energy balance equation.\n"
440
  f"For an adiabatic process, the heat released by the reaction must be absorbed as sensible heat by the products.\n"
441
  f"Heat Absorbed by Products + Heat of Reaction = 0\n"
442
+ f" Σ [ n_i * ∫(Cp_i dT) from {T_initial} to T_ad ] + ΔH_rxn° = 0\n\n"
443
 
444
  f"**Step 4:** Solve the equation for the adiabatic flame temperature (T_ad).\n"
445
+ f"This equation is non-linear because Cp is a function of temperature. We use a numerical solver to find T_ad.\n"
446
+ f"Initial guess: {initial_guess} K\n\n"
447
 
448
+ f"**Step 5:** State the result.\n"
449
+ f"The calculated adiabatic flame temperature is:\n"
450
  f"T_ad = {adiabatic_temp_kelvin:.2f} K\n\n"
451
 
452
  f"**Answer:** The estimated adiabatic flame temperature is **{adiabatic_temp_kelvin:.2f} K**."
data/templates/branches/chemical_engineering/thermodynamics/volumetric_properties_pure_fluids.py CHANGED
@@ -1,7 +1,7 @@
1
  import random
2
  import numpy as np
3
  import math
4
- from data.templates.branches.chemical_engineering.constants import GAS_PHASE_REACTANTS, THERMO_SUBSTANCES, CRITICAL_PROPERTIES
5
 
6
 
7
  # Template 1 (Easy)
@@ -417,108 +417,111 @@ def template_vdw_solve_for_volume():
417
  Scenario:
418
  Solving for molar volume (V) from a cubic equation of state, given
419
  temperature (T) and pressure (P), requires finding the roots of a
420
- cubic polynomial, which is done with a numerical solver.
421
 
422
- When the state (T, P) is below the critical point, the equation can yield
423
- three positive real roots, corresponding to the saturated liquid volume, the
424
- saturated vapor volume, and an unstable intermediate root.
 
 
425
 
426
  The governing equation is cast into a polynomial form for root-finding:
427
- V**3 + c2*V**2 + c1*V + c0 = 0
428
- Where:
429
- - c2 = -(b + R*T/P)
430
- - c1 = a/P
431
- - c0 = -(a*b)/P
432
 
433
  Returns:
434
  tuple: A tuple containing:
435
  - str: A question asking to compute the possible molar volumes.
436
  - str: A step-by-step solution showing the calculation and root analysis.
437
  """
438
- # 1. Parameterize the inputs
 
 
 
439
  R = 0.08314 # L·bar/(mol·K)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
 
441
- substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
442
- properties = CRITICAL_PROPERTIES[substance_name]
443
- Tc = properties["Tc"]
444
- Pc = properties["Pc"]
445
-
446
- # Choose a T and P below the critical point
447
- Tr = random.uniform(0.85, 0.95)
448
- T = round(Tr * Tc, 2)
449
- Pr = random.uniform(Tr * 0.8, Tr * 0.9) # Estimate a realistic saturation pressure
450
- P = round(Pr * Pc, 2)
451
-
452
- # 2. Perform the core calculation
453
- a = (27 * (R**2) * (Tc**2)) / (64 * Pc)
454
- b = (R * Tc) / (8 * Pc)
455
-
456
- # Coefficients of the cubic polynomial: V³ + c₂V² + c₁V + c₀ = 0
457
- c2 = -(b + R * T / P)
458
- c1 = a / P
459
- c0 = -(a * b) / P
460
- coeffs = [1, c2, c1, c0]
461
-
462
- roots = np.roots(coeffs)
463
-
464
- # Improved Root Handling: Filter for nearly-real, positive roots
465
- tolerance = 1e-9
466
- positive_real_roots = sorted([
467
- root.real for root in roots if abs(root.imag) < tolerance and root.real > 0
468
- ])
469
-
470
- V_f, V_g = 0, 0
471
- if len(positive_real_roots) >= 2: # Typically 3 for subcritical
472
- V_f = positive_real_roots[0]
473
- V_g = positive_real_roots[-1] # Safely choose the largest
474
- elif len(positive_real_roots) == 1:
475
- V_g = positive_real_roots[0]
476
 
477
  # 3. Generate the question and solution strings
478
  question = (
479
  f"A vessel of {substance_name} is held at a temperature of {T} K and a pressure of {P} bar. "
480
  f"Using the van der Waals equation of state, determine the possible molar volumes (L/mol) "
481
- f"for the liquid and/or vapor phases. The critical properties for {substance_name} are:\n"
 
482
  f"Tc = {Tc} K\nPc = {Pc} bar"
483
  )
484
 
485
  solution = (
486
  f"**Step 1:** Calculate the van der Waals parameters 'a' and 'b'.\n"
487
- f"a = (27 * R² * Tc²) / (64 * Pc) = {round(a, 4)} L²·bar/mol²\n"
488
  f"b = (R * Tc) / (8 * Pc) = {round(b, 5)} L/mol\n\n"
489
 
490
- f"**Step 2:** Formulate the cubic equation: V³ + c₂V² + c₁V + c₀ = 0.\n"
491
- f"The calculated coefficients, with their respective units, are:\n"
492
- f"- c₂ = {round(c2, 4)} (L/mol)\n"
493
- f"- c₁ = {round(c1, 4)} (L²/mol²)\n"
494
- f"- c₀ = {round(c0, 6)} (L³/mol³)\n\n"
495
 
496
- f"**Step 3:** Solve the polynomial for its roots using a numerical solver.\n"
497
- f"The physically meaningful (positive, real) roots found are: "
498
- f"{', '.join([f'{r:.4f}' for r in positive_real_roots])} L/mol\n\n"
 
499
 
500
  f"**Step 4:** Interpret the physical significance of the roots.\n"
 
 
 
 
 
 
 
 
501
  )
502
 
503
- if len(positive_real_roots) >= 2:
504
- solution += (
505
- f"For a subcritical state, we expect multiple positive real roots.\n"
506
- f"- The smallest root corresponds to the molar volume of the **liquid phase (Vf)**.\n"
507
- f"- The largest root corresponds to the molar volume of the **vapor phase (Vg)**.\n"
508
- f"- Any intermediate root lies on the thermodynamically unstable branch of the isotherm (where pressure incorrectly increases with volume) and is disregarded.\n\n"
509
- f"**Answer:**\n"
510
- f" - Saturated Liquid Volume (Vf) ≈ **{round(V_f, 4)} L/mol**\n"
511
- f" - Saturated Vapor Volume (Vg) ≈ **{round(V_g, 4)} L/mol**"
512
- )
513
- elif len(positive_real_roots) == 1:
514
- solution += (
515
- f"A single positive real root indicates the substance exists in a single phase (gas or supercritical fluid).\n\n"
516
- f"**Answer:**\n"
517
- f" - Molar Volume (V) = **{round(V_g, 4)} L/mol**"
518
- )
519
- else:
520
- solution += "No physically meaningful (positive, real) roots were found for these conditions, which may indicate an issue with the applicability of the model at this state."
521
-
522
  return question, solution
523
 
524
 
 
1
  import random
2
  import numpy as np
3
  import math
4
+ from data.templates.branches.chemical_engineering.constants import GAS_PHASE_REACTANTS, THERMO_SUBSTANCES, CRITICAL_PROPERTIES, REAL_FLUID_DATA
5
 
6
 
7
  # Template 1 (Easy)
 
417
  Scenario:
418
  Solving for molar volume (V) from a cubic equation of state, given
419
  temperature (T) and pressure (P), requires finding the roots of a
420
+ cubic polynomial.
421
 
422
+ When the state (T, P) is within the two-phase region (subcritical),
423
+ the equation yields three real roots:
424
+ 1. Smallest root: Liquid-phase molar volume.
425
+ 2. Largest root: Vapor-phase molar volume.
426
+ 3. Intermediate root: Unstable state (physically meaningless).
427
 
428
  The governing equation is cast into a polynomial form for root-finding:
429
+ V^3 - (b + RT/P)V^2 + (a/P)V - (ab/P) = 0
 
 
 
 
430
 
431
  Returns:
432
  tuple: A tuple containing:
433
  - str: A question asking to compute the possible molar volumes.
434
  - str: A step-by-step solution showing the calculation and root analysis.
435
  """
436
+ # 1. Parameterize the inputs with a Validation Loop
437
+ # We want to ensure we generate a problem with 3 roots (the "Two-Phase" scenario)
438
+ # as this is the most pedagogically valuable case for cubic EOS problems.
439
+
440
  R = 0.08314 # L·bar/(mol·K)
441
+
442
+ # Loop until we find a set of inputs that produces 3 distinct real roots
443
+ while True:
444
+ try:
445
+ substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
446
+ properties = CRITICAL_PROPERTIES[substance_name]
447
+ Tc = properties["Tc"]
448
+ Pc = properties["Pc"]
449
+
450
+ # Choose T well below critical point to ensure the "loop" is wide enough
451
+ Tr = random.uniform(0.65, 0.85)
452
+ T = round(Tr * Tc, 2)
453
+
454
+ # Estimate VdW saturation pressure to hit the 3-root region
455
+ # Approx VdW vapor pressure: log10(Pr) ~ -3(1/Tr - 1)
456
+ approx_Pr_sat = 10**(-3.0 * (1.0/Tr - 1.0))
457
+
458
+ # Pick P close to this saturation pressure
459
+ Pr = approx_Pr_sat * random.uniform(0.95, 1.05)
460
+ P = round(Pr * Pc, 3) # Use 3 decimals for precision
461
+
462
+ # 2. Perform the core calculation
463
+ a = (27 * (R**2) * (Tc**2)) / (64 * Pc)
464
+ b = (R * Tc) / (8 * Pc)
465
 
466
+ # Coefficients of the cubic polynomial: V³ + c₂V² + c₁V + c₀ = 0
467
+ c2 = -(b + R * T / P)
468
+ c1 = a / P
469
+ c0 = -(a * b) / P
470
+ coeffs = [1, c2, c1, c0]
471
+
472
+ roots = np.roots(coeffs)
473
+
474
+ # Filter for positive, real roots
475
+ tolerance = 1e-9
476
+ positive_real_roots = sorted([
477
+ root.real for root in roots if abs(root.imag) < tolerance and root.real > 0
478
+ ])
479
+
480
+ # If we found 3 roots, we have a valid problem. Break the loop.
481
+ if len(positive_real_roots) == 3:
482
+ V_f = positive_real_roots[0]
483
+ V_g = positive_real_roots[-1]
484
+ break
485
+
486
+ except Exception:
487
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
488
 
489
  # 3. Generate the question and solution strings
490
  question = (
491
  f"A vessel of {substance_name} is held at a temperature of {T} K and a pressure of {P} bar. "
492
  f"Using the van der Waals equation of state, determine the possible molar volumes (L/mol) "
493
+ f"predicted for the liquid and vapor phases.\n\n"
494
+ f"The critical properties for {substance_name} are:\n"
495
  f"Tc = {Tc} K\nPc = {Pc} bar"
496
  )
497
 
498
  solution = (
499
  f"**Step 1:** Calculate the van der Waals parameters 'a' and 'b'.\n"
500
+ f"a = (27 * R^2 * Tc^2) / (64 * Pc) = {round(a, 4)} L^2·bar/mol^2\n"
501
  f"b = (R * Tc) / (8 * Pc) = {round(b, 5)} L/mol\n\n"
502
 
503
+ f"**Step 2:** Formulate the cubic equation: V^3 + c2*V^2 + c1*V + c0 = 0.\n"
504
+ f"The coefficients are derived from the VdW equation:\n"
505
+ f"c2 = -(b + RT/P) = {round(c2, 4)} L/mol\n"
506
+ f"c1 = a/P = {round(c1, 4)} L^2/mol^2\n"
507
+ f"c0 = -(ab)/P = {round(c0, 6)} L^3/mol^3\n\n"
508
 
509
+ f"**Step 3:** Solve the polynomial for its roots.\n"
510
+ f"Using a numerical solver, we find three real, positive roots:\n"
511
+ f"Roots: {', '.join([f'{r:.4f}' for r in positive_real_roots])} L/mol\n\n"
512
+ f"\n\n"
513
 
514
  f"**Step 4:** Interpret the physical significance of the roots.\n"
515
+ f"For a state in the subcritical region, the EOS predicts three roots:\n"
516
+ f"- The **smallest root** corresponds to the molar volume of the **Liquid Phase**.\n"
517
+ f"- The **largest root** corresponds to the molar volume of the **Vapor Phase**.\n"
518
+ f"- The intermediate root is physically unstable and disregarded.\n\n"
519
+
520
+ f"**Answer:**\n"
521
+ f"1. Liquid-phase Volume (V_liq) ≈ **{round(V_f, 4)} L/mol**\n"
522
+ f"2. Vapor-phase Volume (V_vap) ≈ **{round(V_g, 4)} L/mol**"
523
  )
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  return question, solution
526
 
527
 
data/templates/branches/chemical_engineering/transport_phenomena/__pycache__/shell_momentum_balances.cpython-311.pyc ADDED
Binary file (16.5 kB). View file
 
data/templates/branches/chemical_engineering/transport_phenomena/__pycache__/viscosity_and_momentum_transport.cpython-311.pyc ADDED
Binary file (22.8 kB). View file
 
data/templates/branches/chemical_engineering/transport_phenomena/shell_momentum_balances.py CHANGED
@@ -11,9 +11,7 @@ def template_falling_film_max_velocity():
11
  Scenario:
12
  This template calculates the maximum velocity of a liquid film flowing
13
  down an inclined plane under gravity. The maximum velocity occurs at the
14
- free surface (the liquid-air interface), where shear stress is zero.
15
- The calculation is based on the final velocity profile equation derived
16
- from a shell momentum balance for this system.
17
 
18
  Core Equation:
19
  v_z_max = (rho * g * delta^2 * cos(beta)) / (2 * mu)
@@ -23,30 +21,38 @@ def template_falling_film_max_velocity():
23
  - str: A question asking to compute the maximum film velocity.
24
  - str: A step-by-step solution showing the calculation.
25
  """
26
- # 1. Parameterize the inputs with random values
27
- # Choose a random fluid name (key) from the dictionary
28
- fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
29
- # Get the corresponding properties (the tuple of density and viscosity)
30
- fluid_density, fluid_viscosity = COMMON_LIQUIDS[fluid_name]
31
-
32
- # Film thickness in mm
33
- film_thickness_mm = round(random.uniform(0.5, 5.0), 2)
34
-
35
- # Angle of inclination from the vertical in degrees
36
- inclination_angle_deg = random.randint(5, 85)
37
-
38
- # Constants
39
  g = GRAVITATIONAL_ACCELERATION
40
-
41
- # 2. Perform unit conversions and core calculation
42
- # Convert film thickness from mm to m
43
- film_thickness_m = film_thickness_mm / 1000.0
44
-
45
- # Convert inclination angle from degrees to radians for math functions
46
- inclination_angle_rad = math.radians(inclination_angle_deg)
47
 
48
- # Calculate the maximum velocity
49
- v_max = (fluid_density * g * (film_thickness_m**2) * math.cos(inclination_angle_rad)) / (2 * fluid_viscosity)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  # 3. Generate the question and solution strings
52
  question = (
@@ -75,7 +81,8 @@ def template_falling_film_max_velocity():
75
 
76
  f"**Step 3:** State the Core Equation\n"
77
  f"For a laminar falling film, the maximum velocity (v_z_max) at the liquid-air interface is given by the equation derived from the shell momentum balance:\n"
78
- f"v_z_max = (rho * g * delta^2 * cos(beta)) / (2 * mu)\n\n"
 
79
 
80
  f"**Step 4:** Substitute Values into the Equation\n"
81
  f"Now, we substitute our known values (with correct units) into the equation.\n"
@@ -110,30 +117,43 @@ def template_hagen_poiseuille_flowrate():
110
  - str: A question asking to compute the volumetric flow rate.
111
  - str: A step-by-step solution showing the calculation.
112
  """
113
- # 1. Parameterize the inputs with random values
114
- # Choose a random fluid name (key) from the dictionary
115
- fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
116
- # Get the corresponding properties (the tuple of density and viscosity)
117
- fluid_density, fluid_viscosity_Pas = COMMON_LIQUIDS[fluid_name]
118
-
119
- # Pipe radius in cm
120
- pipe_radius_cm = round(random.uniform(0.5, 5.0), 2)
121
- # Pipe length in m
122
- pipe_length_m = round(random.uniform(5.0, 100.0), 1)
123
- # Pressure drop in kPa
124
- pressure_drop_kPa = random.randint(50, 500)
125
- # Convert viscosity to cP for the problem statement (1 Pa·s = 1000 cP)
126
- fluid_viscosity_cP = fluid_viscosity_Pas * 1000
127
-
128
- # 2. Perform unit conversions and core calculation
129
- # Convert radius from cm to m
130
- pipe_radius_m = pipe_radius_cm / 100.0
131
- # Convert pressure drop from kPa to Pa
132
- pressure_drop_Pa = pressure_drop_kPa * 1000
133
-
134
- # Calculate the volumetric flow rate (Q)
135
- # Q = (pi * delta_P * R^4) / (8 * mu * L)
136
- q_flow_rate = (math.pi * pressure_drop_Pa * (pipe_radius_m**4)) / (8 * fluid_viscosity_Pas * pipe_length_m)
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  # 3. Generate the question and solution strings
139
  question = (
@@ -184,47 +204,63 @@ def template_annulus_flowrate():
184
 
185
  Scenario:
186
  This template calculates the volumetric flow rate (Q) for a fluid in
187
- laminar flow through the concentric cylindrical region between two pipes (an annulus).
188
- The equation is derived from the shell momentum balance for this specific geometry.
189
 
190
  Core Equation:
191
- Q = (pi*(P0-PL)*R^4)/(8*mu*L) * [(1-kappa^4) - ((1-kappa^2)^2 / ln(1/kappa))]
192
 
193
  Returns:
194
  tuple: A tuple containing:
195
- - str: A question asking to compute the volumetric flow rate in an annulus.
196
  - str: A step-by-step solution showing the calculation.
197
  """
198
- # 1. Parameterize the inputs with random values
199
- # Choose a random fluid name (key) from the dictionary
200
  fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
201
- # Get the corresponding properties (the tuple of density and viscosity)
202
  fluid_density, fluid_viscosity_Pas = COMMON_LIQUIDS[fluid_name]
203
 
204
- # Define radii and ensure inner radius is smaller than outer radius
205
- outer_radius_cm = round(random.uniform(2.0, 10.0), 2)
206
- kappa = round(random.uniform(0.2, 0.8), 2) # Ratio of inner to outer radius
207
  inner_radius_cm = round(kappa * outer_radius_cm, 2)
208
-
209
- # Pipe length in m
210
- pipe_length_m = round(random.uniform(10.0, 150.0), 1)
211
- # Pressure drop in kPa
212
- pressure_drop_kPa = random.randint(100, 1000)
213
-
214
- # 2. Perform unit conversions and core calculation
215
- # Convert radii from cm to m
216
  outer_radius_m = outer_radius_cm / 100.0
217
  inner_radius_m = inner_radius_cm / 100.0
 
218
 
219
- # Convert pressure drop from kPa to Pa
220
- pressure_drop_Pa = pressure_drop_kPa * 1000
221
 
222
- # The dimensionless ratio, kappa
223
- kappa_val = inner_radius_m / outer_radius_m
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
- # Calculate the flow rate using the annulus equation
226
- term1 = (math.pi * pressure_drop_Pa * (outer_radius_m**4)) / (8 * fluid_viscosity_Pas * pipe_length_m)
227
  shape_factor = (1 - kappa_val**4) - (((1 - kappa_val**2)**2) / math.log(1 / kappa_val))
 
 
 
 
 
 
 
 
 
 
 
 
228
  q_flow_rate = term1 * shape_factor
229
 
230
  # 3. Generate the question and solution strings
@@ -232,7 +268,7 @@ def template_annulus_flowrate():
232
  f"Laminar flow of {fluid_name} occurs in the annular space between two concentric pipes. "
233
  f"The inner pipe has an outer radius of {inner_radius_cm} cm, and the outer pipe has an inner radius of {outer_radius_cm} cm. "
234
  f"The concentric pipes have a length of {pipe_length_m} m.\n\n"
235
- f"A pressure drop of {pressure_drop_kPa} kPa is maintained over the length of the pipes. "
236
  f"The fluid viscosity is {fluid_viscosity_Pas} Pa·s.\n\n"
237
  f"Calculate the volumetric flow rate (Q) through the annular space in m^3/s."
238
  )
@@ -244,31 +280,34 @@ def template_annulus_flowrate():
244
  f"- Inner Radius (R_inner): {inner_radius_cm} cm\n"
245
  f"- Outer Radius (R_outer): {outer_radius_cm} cm\n"
246
  f"- Pipe Length (L): {pipe_length_m} m\n"
247
- f"- Pressure Drop (P0 - PL): {pressure_drop_kPa} kPa\n"
248
- f"- Fluid Viscosity (mu): {fluid_viscosity_Pas} Pa·s\n\n"
 
249
 
250
  f"**Step 2:** Perform Unit Conversions\n"
251
- f"The equation requires all units to be in the SI base system. We must convert the radii and the pressure drop.\n"
252
- f"- Inner Radius: R_inner = {inner_radius_cm} cm * (1 m / 100 cm) = {inner_radius_m} m\n"
253
- f"- Outer Radius: R_outer = {outer_radius_cm} cm * (1 m / 100 cm) = {outer_radius_m} m\n"
254
- f"- Pressure Drop: P0 - PL = {pressure_drop_kPa} kPa * (1000 Pa / 1 kPa) = {pressure_drop_Pa} Pa\n\n"
255
 
256
  f"**Step 3:** Calculate the Dimensionless Ratio (kappa)\n"
257
  f"Kappa (k) is the ratio of the inner radius to the outer radius.\n"
258
- f"kappa = R_inner / R_outer = {inner_radius_m} m / {outer_radius_m} m = {kappa_val:.3f}\n\n"
259
 
260
  f"**Step 4:** State the Core Equation\n"
261
  f"The volumetric flow rate (Q) for laminar flow in an annulus is:\n"
262
- f"Q = (pi * (P0 - PL) * R_outer^4) / (8 * mu * L) * [ (1 - kappa^4) - ((1 - kappa^2)^2 / ln(1/kappa)) ]\n\n"
263
 
264
  f"**Step 5:** Substitute Values and Calculate\n"
265
- f"We substitute the converted SI values into the equation. Let's calculate the main term and the shape factor separately for clarity.\n"
266
- f"Main Term = (pi * {pressure_drop_Pa} Pa * ({outer_radius_m} m)^4) / (8 * {fluid_viscosity_Pas} Pa·s * {pipe_length_m} m) = {term1:.6f}\n"
 
 
267
  f"Shape Factor = [ (1 - {kappa_val:.3f}^4) - ((1 - {kappa_val:.3f}^2)^2 / ln(1/{kappa_val:.3f})) ] = {shape_factor:.4f}\n\n"
268
  f"Q = Main Term * Shape Factor\n"
269
- f"Q = {term1:.6f} * {shape_factor:.4f} = {q_flow_rate:.7f} m^3/s\n\n"
270
 
271
- f"Answer: The volumetric flow rate of {fluid_name} through the annular space is {q_flow_rate:.7f} m^3/s."
272
  )
273
 
274
  return question, solution
 
11
  Scenario:
12
  This template calculates the maximum velocity of a liquid film flowing
13
  down an inclined plane under gravity. The maximum velocity occurs at the
14
+ free surface (the liquid-air interface).
 
 
15
 
16
  Core Equation:
17
  v_z_max = (rho * g * delta^2 * cos(beta)) / (2 * mu)
 
21
  - str: A question asking to compute the maximum film velocity.
22
  - str: A step-by-step solution showing the calculation.
23
  """
24
+ # 1. Parameterize the inputs with Validation Loop for Physical Plausibility
 
 
 
 
 
 
 
 
 
 
 
 
25
  g = GRAVITATIONAL_ACCELERATION
 
 
 
 
 
 
 
26
 
27
+ while True:
28
+ # Choose a random fluid
29
+ fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
30
+ fluid_density, fluid_viscosity = COMMON_LIQUIDS[fluid_name]
31
+
32
+ # Film thickness in mm (Reduced range for realistic laminar films)
33
+ film_thickness_mm = round(random.uniform(0.1, 2.0), 2)
34
+
35
+ # Angle of inclination from the vertical in degrees
36
+ inclination_angle_deg = random.randint(5, 85)
37
+
38
+ # Perform check calculation
39
+ film_thickness_m = film_thickness_mm / 1000.0
40
+ inclination_angle_rad = math.radians(inclination_angle_deg)
41
+
42
+ v_max_check = (fluid_density * g * (film_thickness_m**2) * math.cos(inclination_angle_rad)) / (2 * fluid_viscosity)
43
+
44
+ # PHYSICS CHECK:
45
+ # 1. Velocity should be reasonable (e.g., < 10 m/s)
46
+ # 2. Reynolds number (Re = 4*rho*v_avg*delta / mu) should be laminar (< ~1000-2000)
47
+ # v_avg = (2/3) * v_max
48
+ v_avg = (2/3) * v_max_check
49
+ Re = (4 * fluid_density * v_avg * film_thickness_m) / fluid_viscosity
50
+
51
+ if v_max_check < 10.0 and Re < 1500:
52
+ break # Scenario is valid
53
+
54
+ # 2. Perform the core calculation (already done in check, just finalize)
55
+ v_max = v_max_check
56
 
57
  # 3. Generate the question and solution strings
58
  question = (
 
81
 
82
  f"**Step 3:** State the Core Equation\n"
83
  f"For a laminar falling film, the maximum velocity (v_z_max) at the liquid-air interface is given by the equation derived from the shell momentum balance:\n"
84
+ f"v_z_max = (rho * g * delta^2 * cos(beta)) / (2 * mu)\n"
85
+ f"\n\n"
86
 
87
  f"**Step 4:** Substitute Values into the Equation\n"
88
  f"Now, we substitute our known values (with correct units) into the equation.\n"
 
117
  - str: A question asking to compute the volumetric flow rate.
118
  - str: A step-by-step solution showing the calculation.
119
  """
120
+ # 1. Parameterize the inputs with a loop to ensure Laminar Flow (Re < 2300)
121
+ while True:
122
+ # Choose a random fluid name (key) from the dictionary
123
+ fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
124
+ # Get the corresponding properties (the tuple of density and viscosity)
125
+ fluid_density, fluid_viscosity_Pas = COMMON_LIQUIDS[fluid_name]
126
+
127
+ # Pipe radius in cm
128
+ pipe_radius_cm = round(random.uniform(0.5, 5.0), 2)
129
+ # Pipe length in m
130
+ pipe_length_m = round(random.uniform(5.0, 100.0), 1)
131
+ # Pressure drop in kPa
132
+ pressure_drop_kPa = random.randint(50, 500)
133
+ # Convert viscosity to cP for the problem statement (1 Pa·s = 1000 cP)
134
+ fluid_viscosity_cP = fluid_viscosity_Pas * 1000
135
+
136
+ # 2. Perform unit conversions and core calculation
137
+ # Convert radius from cm to m
138
+ pipe_radius_m = pipe_radius_cm / 100.0
139
+ # Convert pressure drop from kPa to Pa
140
+ pressure_drop_Pa = pressure_drop_kPa * 1000
141
+
142
+ # Calculate the volumetric flow rate (Q)
143
+ # Q = (pi * delta_P * R^4) / (8 * mu * L)
144
+ q_flow_rate = (math.pi * pressure_drop_Pa * (pipe_radius_m**4)) / (8 * fluid_viscosity_Pas * pipe_length_m)
145
+
146
+ # Check Reynolds Number
147
+ # V_avg = Q / Area = Q / (pi * R^2)
148
+ v_avg = q_flow_rate / (math.pi * pipe_radius_m**2)
149
+ # Diameter = 2 * Radius
150
+ diameter_m = 2 * pipe_radius_m
151
+ # Re = (rho * V_avg * D) / mu
152
+ reynolds_number = (fluid_density * v_avg * diameter_m) / fluid_viscosity_Pas
153
+
154
+ # Only accept if flow is laminar
155
+ if reynolds_number < 2300:
156
+ break
157
 
158
  # 3. Generate the question and solution strings
159
  question = (
 
204
 
205
  Scenario:
206
  This template calculates the volumetric flow rate (Q) for a fluid in
207
+ laminar flow through the concentric cylindrical region between two pipes.
 
208
 
209
  Core Equation:
210
+ Q = (pi*(P0-PL)*R^4)/(8*mu*L) * [(1-k^4) - ((1-k^2)^2 / ln(1/k))]
211
 
212
  Returns:
213
  tuple: A tuple containing:
214
+ - str: A question asking to compute the volumetric flow rate.
215
  - str: A step-by-step solution showing the calculation.
216
  """
217
+ # 1. Parameterize inputs with Physical Plausibility Check
218
+ # Choose a random fluid
219
  fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
 
220
  fluid_density, fluid_viscosity_Pas = COMMON_LIQUIDS[fluid_name]
221
 
222
+ # Define radii
223
+ outer_radius_cm = round(random.uniform(2.0, 5.0), 2)
224
+ kappa = round(random.uniform(0.3, 0.7), 2)
225
  inner_radius_cm = round(kappa * outer_radius_cm, 2)
226
+
227
+ # Convert to meters
 
 
 
 
 
 
228
  outer_radius_m = outer_radius_cm / 100.0
229
  inner_radius_m = inner_radius_cm / 100.0
230
+ kappa_val = inner_radius_m / outer_radius_m
231
 
232
+ pipe_length_m = round(random.uniform(10.0, 50.0), 1)
 
233
 
234
+ # Ensure Laminar Flow (Re < 2100)
235
+ # Instead of random Pressure Drop, we pick a target Reynolds number
236
+ # Hydraulic Diameter Dh = 2 * (Ro - Ri)
237
+ Dh = 2 * (outer_radius_m - inner_radius_m)
238
+
239
+ # Target Re between 500 and 1500 (safely laminar)
240
+ Re_target = random.uniform(500, 1500)
241
+
242
+ # Calculate required velocity: v = (Re * mu) / (rho * Dh)
243
+ velocity_target = (Re_target * fluid_viscosity_Pas) / (fluid_density * Dh)
244
+
245
+ # Calculate required Flow Rate: Q = v * Area
246
+ area = math.pi * (outer_radius_m**2 - inner_radius_m**2)
247
+ Q_target = velocity_target * area
248
 
249
+ # Back-calculate the Pressure Drop required to drive this Q
250
+ # Inverted Annulus Equation: DeltaP = Q * (8*mu*L) / (pi*Ro^4 * ShapeFactor)
251
  shape_factor = (1 - kappa_val**4) - (((1 - kappa_val**2)**2) / math.log(1 / kappa_val))
252
+ denominator = (math.pi * (outer_radius_m**4)) * shape_factor
253
+ numerator = Q_target * (8 * fluid_viscosity_Pas * pipe_length_m)
254
+
255
+ pressure_drop_Pa_exact = numerator / denominator
256
+
257
+ # Round Pressure Drop to look "given" (e.g., nearest 10 Pa)
258
+ pressure_drop_Pa = round(pressure_drop_Pa_exact / 10.0) * 10.0
259
+ if pressure_drop_Pa == 0: pressure_drop_Pa = 10.0
260
+ pressure_drop_kPa = pressure_drop_Pa / 1000.0
261
+
262
+ # 2. Perform the forward calculation with rounded inputs
263
+ term1 = (math.pi * pressure_drop_Pa * (outer_radius_m**4)) / (8 * fluid_viscosity_Pas * pipe_length_m)
264
  q_flow_rate = term1 * shape_factor
265
 
266
  # 3. Generate the question and solution strings
 
268
  f"Laminar flow of {fluid_name} occurs in the annular space between two concentric pipes. "
269
  f"The inner pipe has an outer radius of {inner_radius_cm} cm, and the outer pipe has an inner radius of {outer_radius_cm} cm. "
270
  f"The concentric pipes have a length of {pipe_length_m} m.\n\n"
271
+ f"A pressure drop of {pressure_drop_kPa:.3f} kPa is maintained over the length of the pipes. "
272
  f"The fluid viscosity is {fluid_viscosity_Pas} Pa·s.\n\n"
273
  f"Calculate the volumetric flow rate (Q) through the annular space in m^3/s."
274
  )
 
280
  f"- Inner Radius (R_inner): {inner_radius_cm} cm\n"
281
  f"- Outer Radius (R_outer): {outer_radius_cm} cm\n"
282
  f"- Pipe Length (L): {pipe_length_m} m\n"
283
+ f"- Pressure Drop (P0 - PL): {pressure_drop_kPa:.3f} kPa\n"
284
+ f"- Fluid Viscosity (mu): {fluid_viscosity_Pas} Pa·s\n"
285
+ f"\n\n"
286
 
287
  f"**Step 2:** Perform Unit Conversions\n"
288
+ f"The equation requires all units to be in the SI base system.\n"
289
+ f"- Inner Radius: R_inner = {inner_radius_cm} cm / 100 = {inner_radius_m} m\n"
290
+ f"- Outer Radius: R_outer = {outer_radius_cm} cm / 100 = {outer_radius_m} m\n"
291
+ f"- Pressure Drop: P0 - PL = {pressure_drop_kPa:.3f} kPa * 1000 = {pressure_drop_Pa} Pa\n\n"
292
 
293
  f"**Step 3:** Calculate the Dimensionless Ratio (kappa)\n"
294
  f"Kappa (k) is the ratio of the inner radius to the outer radius.\n"
295
+ f"kappa = R_inner / R_outer = {inner_radius_m} / {outer_radius_m} = {kappa_val:.3f}\n\n"
296
 
297
  f"**Step 4:** State the Core Equation\n"
298
  f"The volumetric flow rate (Q) for laminar flow in an annulus is:\n"
299
+ f"Q = (pi * (P0 - PL) * R_outer^4) / (8 * mu * L) * [ (1 - k^4) - ((1 - k^2)^2 / ln(1/k)) ]\n\n"
300
 
301
  f"**Step 5:** Substitute Values and Calculate\n"
302
+ f"Calculate the main term:\n"
303
+ f"Main Term = (pi * {pressure_drop_Pa} * ({outer_radius_m})^4) / (8 * {fluid_viscosity_Pas} * {pipe_length_m})\n"
304
+ f"Main Term = {term1:.6e}\n\n"
305
+ f"Calculate the shape factor:\n"
306
  f"Shape Factor = [ (1 - {kappa_val:.3f}^4) - ((1 - {kappa_val:.3f}^2)^2 / ln(1/{kappa_val:.3f})) ] = {shape_factor:.4f}\n\n"
307
  f"Q = Main Term * Shape Factor\n"
308
+ f"Q = {term1:.6e} * {shape_factor:.4f} = {q_flow_rate:.4e} m^3/s\n\n"
309
 
310
+ f"**Answer:** The volumetric flow rate is **{q_flow_rate:.4e} m^3/s**."
311
  )
312
 
313
  return question, solution
data/templates/branches/chemical_engineering/transport_phenomena/viscosity_and_momentum_transport.py CHANGED
@@ -97,7 +97,7 @@ def template_kinematic_viscosity():
97
  Returns:
98
  tuple: A tuple containing:
99
  - str: A question asking to compute the kinematic viscosity.
100
- - str: A step-by-step solution showing the calculation and unit conversion.
101
  """
102
  # 1. Parameterize the inputs with random values
103
 
@@ -135,6 +135,7 @@ def template_kinematic_viscosity():
135
  f"**Step 1:** State the formula for kinematic viscosity.\n"
136
  f"Kinematic viscosity (ν) is defined as the ratio of dynamic viscosity to density:\n"
137
  f"ν = μ / ρ\n\n"
 
138
 
139
  f"**Step 2:** Calculate the kinematic viscosity in SI units (m²/s).\n"
140
  f"Substitute the given values into the formula:\n"
@@ -144,16 +145,18 @@ def template_kinematic_viscosity():
144
 
145
  # Add the unit conversion step only if needed
146
  if "Stokes" in target_units:
 
147
  solution += (
148
  f"**Step 3:** Convert the result to Stokes (St).\n"
149
  f"The conversion factor is 1 m²/s = 10,000 St.\n"
150
  f"ν = ({nu_si:.3e} m²/s) * (10,000 St / 1 m²/s)\n"
151
- f"ν = {final_nu:.4f} St\n\n"
152
  )
153
 
154
  solution += (
155
  f"**Answer:**\n"
156
- f"The kinematic viscosity of {fluid_name} is **{final_nu:.4f} {target_units}**."
 
157
  )
158
 
159
  return question, solution
 
97
  Returns:
98
  tuple: A tuple containing:
99
  - str: A question asking to compute the kinematic viscosity.
100
+ - str: A step-by-step solution showing the calculation.
101
  """
102
  # 1. Parameterize the inputs with random values
103
 
 
135
  f"**Step 1:** State the formula for kinematic viscosity.\n"
136
  f"Kinematic viscosity (ν) is defined as the ratio of dynamic viscosity to density:\n"
137
  f"ν = μ / ρ\n\n"
138
+ f"\n\n"
139
 
140
  f"**Step 2:** Calculate the kinematic viscosity in SI units (m²/s).\n"
141
  f"Substitute the given values into the formula:\n"
 
145
 
146
  # Add the unit conversion step only if needed
147
  if "Stokes" in target_units:
148
+ # Fixed formatting here to avoid 0.0000 for small values
149
  solution += (
150
  f"**Step 3:** Convert the result to Stokes (St).\n"
151
  f"The conversion factor is 1 m²/s = 10,000 St.\n"
152
  f"ν = ({nu_si:.3e} m²/s) * (10,000 St / 1 m²/s)\n"
153
+ f"ν = {final_nu:.4e} St\n\n"
154
  )
155
 
156
  solution += (
157
  f"**Answer:**\n"
158
+ # Fixed formatting here to avoid 0.0000 for small values
159
+ f"The kinematic viscosity of {fluid_name} is **{final_nu:.4e} {target_units}**."
160
  )
161
 
162
  return question, solution
data/templates/branches/electrical_engineering/__pycache__/constants.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/electrical_engineering/__pycache__/constants.cpython-311.pyc and b/data/templates/branches/electrical_engineering/__pycache__/constants.cpython-311.pyc differ
 
data/templates/branches/electrical_engineering/digital_communications/__pycache__/deterministic_and_random_signal_analysis.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/electrical_engineering/digital_communications/__pycache__/deterministic_and_random_signal_analysis.cpython-311.pyc and b/data/templates/branches/electrical_engineering/digital_communications/__pycache__/deterministic_and_random_signal_analysis.cpython-311.pyc differ
 
data/templates/branches/electrical_engineering/digital_communications/__pycache__/digital_modulation_schemes.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/electrical_engineering/digital_communications/__pycache__/digital_modulation_schemes.cpython-311.pyc and b/data/templates/branches/electrical_engineering/digital_communications/__pycache__/digital_modulation_schemes.cpython-311.pyc differ
 
data/templates/branches/electrical_engineering/digital_communications/digital_modulation_schemes.py CHANGED
@@ -39,20 +39,18 @@ def template_bpsk_energy_basis():
39
  k = random.randint(2, 20)
40
  carrier_freq_hz = k / bit_duration_s
41
 
42
- # --- Start: Inline formatting for bit_duration_s ---
43
  prefixes = {6: 'M', 3: 'k', 0: '', -3: 'm', -6: 'u', -9: 'n'}
44
  exponent_td = int(math.floor(math.log10(abs(bit_duration_s)) / 3.0) * 3)
45
  prefix_td = prefixes.get(exponent_td, '')
46
  scaled_td = bit_duration_s / (10**exponent_td)
47
  bit_duration_str = f"{round(scaled_td, precision)} {prefix_td}s"
48
- # --- End: Inline formatting ---
49
 
50
- # --- Start: Inline formatting for carrier_freq_hz ---
51
  exponent_fc = int(math.floor(math.log10(abs(carrier_freq_hz)) / 3.0) * 3)
52
  prefix_fc = prefixes.get(exponent_fc, '')
53
  scaled_fc = carrier_freq_hz / (10**exponent_fc)
54
  carrier_freq_str = f"{round(scaled_fc, precision)} {prefix_fc}Hz"
55
- # --- End: Inline formatting ---
56
 
57
  # 2. Perform the core calculation
58
 
@@ -62,12 +60,11 @@ def template_bpsk_energy_basis():
62
  # Calculate the amplitude of the basis function: sqrt(2 / Tb)
63
  basis_amplitude = math.sqrt(2 / bit_duration_s)
64
 
65
- # --- Start: Inline formatting for energy_joules ---
66
  exponent_e = int(math.floor(math.log10(abs(energy_joules)) / 3.0) * 3)
67
  prefix_e = prefixes.get(exponent_e, '')
68
  scaled_e = energy_joules / (10**exponent_e)
69
  energy_str = f"{round(scaled_e, precision)} {prefix_e}J"
70
- # --- End: Inline formatting ---
71
 
72
  # 3. Generate the question and solution strings
73
 
@@ -126,21 +123,11 @@ def template_euclidean_distance_binary():
126
  Scenario:
127
  This template tests the ability to represent signals as vectors in a signal
128
  space and calculate the Euclidean distance between them. This distance is a
129
- key factor in determining the noise immunity of a modulation scheme, as a
130
- larger distance between signal points implies better performance in the
131
- presence of noise. The problem covers two fundamental binary schemes:
132
- antipodal (BPSK) and orthogonal (BFSK).
133
 
134
  Core Equations:
135
- For BPSK (antipodal signals):
136
- s1 = (sqrt(Eb))
137
- s2 = (-sqrt(Eb))
138
- d = 2 * sqrt(Eb)
139
-
140
- For Orthogonal BFSK:
141
- s1 = (sqrt(Eb), 0)
142
- s2 = (0, sqrt(Eb))
143
- d = sqrt(2 * Eb)
144
 
145
  Returns:
146
  tuple: A tuple containing:
@@ -155,43 +142,47 @@ def template_euclidean_distance_binary():
155
  # Generate energy per bit, Eb, in a range from picojoules to nanojoules
156
  energy_joules = random.uniform(1e-12, 1e-9)
157
 
158
- # --- Start: Inline formatting for energy_joules ---
159
  prefixes = {0: '', -3: 'm', -6: 'u', -9: 'n', -12: 'p'}
160
  if energy_joules > 0:
161
  exponent_e = int(math.floor(math.log10(abs(energy_joules)) / 3.0) * 3)
162
  prefix_e = prefixes.get(exponent_e, '')
163
  scaled_e = energy_joules / (10**exponent_e)
164
- energy_str = f"{round(scaled_e, precision)} {prefix_e}J"
165
  else:
166
  energy_str = "0 J"
167
- # --- End: Inline formatting ---
168
 
169
  # 2. Perform the core calculation based on modulation type
170
 
171
  sqrt_eb = math.sqrt(energy_joules)
172
 
 
 
 
173
  if modulation_type == 'BPSK':
174
  # BPSK signals are antipodal (180 degrees apart)
175
  constellation_type = "antipodal"
176
  dimension = "one-dimensional"
177
  basis_count = "one basis function, psi_1(t)"
178
 
179
- s1_str = f"({sqrt_eb:.{precision}e})"
180
- s2_str = f"(-{sqrt_eb:.{precision}e})"
181
 
182
  distance = 2 * sqrt_eb
 
183
 
184
  derivation_steps = (
185
  f"The signal vectors are s1 = (sqrt(Eb)) and s2 = (-sqrt(Eb)).\n"
186
- f"s1 = ({round(sqrt_eb, precision)})\n"
187
- f"s2 = (-{round(sqrt_eb, precision)})\n\n"
 
188
 
189
  f"**Step 2:** Calculate Euclidean Distance (d)\n"
190
  f"The distance d is the magnitude of the difference vector (s1 - s2).\n"
191
  f"s1 - s2 = (sqrt(Eb) - (-sqrt(Eb))) = (2*sqrt(Eb))\n"
192
  f"d = ||s1 - s2|| = 2*sqrt(Eb)\n"
193
- f"d = 2 * {round(sqrt_eb, precision)}\n"
194
- f"d = {round(distance, precision)}"
195
  )
196
 
197
  else: # Orthogonal BFSK
@@ -200,22 +191,24 @@ def template_euclidean_distance_binary():
200
  dimension = "two-dimensional"
201
  basis_count = "two basis functions, psi_1(t) and psi_2(t)"
202
 
203
- s1_str = f"({sqrt_eb:.{precision}e}, 0)"
204
- s2_str = f"(0, {sqrt_eb:.{precision}e})"
205
 
206
  distance = math.sqrt(2 * energy_joules)
 
207
 
208
  derivation_steps = (
209
  f"The signal vectors are s1 = (sqrt(Eb), 0) and s2 = (0, sqrt(Eb)).\n"
210
- f"s1 = ({round(sqrt_eb, precision)}, 0)\n"
211
- f"s2 = (0, {round(sqrt_eb, precision)})\n\n"
 
212
 
213
  f"**Step 2: ** Calculate Euclidean Distance (d)\n"
214
  f"The distance d is the magnitude of the difference vector (s1 - s2).\n"
215
  f"s1 - s2 = (sqrt(Eb) - 0, 0 - sqrt(Eb)) = (sqrt(Eb), -sqrt(Eb))\n"
216
  f"d = ||s1 - s2|| = sqrt( (sqrt(Eb))^2 + (-sqrt(Eb))^2 ) = sqrt(2*Eb)\n"
217
  f"d = sqrt(2 * {energy_joules:.{precision}e})\n"
218
- f"d = {round(distance, precision)}"
219
  )
220
 
221
  # 3. Generate the question and solution strings
@@ -235,14 +228,14 @@ def template_euclidean_distance_binary():
235
 
236
  f"**Step 1:** Define Signal Vectors\n"
237
  f"For {modulation_type} modulation, the signals are {constellation_type}. This means we can represent them in a {dimension} signal space using {basis_count}.\n"
238
- f"First, we calculate sqrt(Eb) = sqrt({energy_joules:.{precision}e}) = {round(sqrt_eb, precision)}.\n"
239
  f"{derivation_steps}\n\n"
240
 
241
  f"**Answer:**\n"
242
  f"a) The signal constellation points are:\n"
243
  f"s1 = {s1_str}\n"
244
  f"s2 = {s2_str}\n"
245
- f"b) The Euclidean distance between the points is {round(distance, precision)}."
246
  )
247
 
248
  return question, solution
@@ -254,39 +247,31 @@ def template_average_energy_mqam():
254
  Average Energy of an M-QAM Constellation
255
 
256
  Scenario:
257
- For designing power-efficient transmitters, understanding the average energy
258
- per transmitted symbol is crucial. Unlike M-PSK where all symbols have the
259
- same energy, square M-QAM constellations have symbols with varying energies.
260
  This template tests the ability to calculate the average symbol energy by
261
  analyzing the geometry of the constellation.
262
 
263
  Core Equations:
264
- Coordinates: (xi, yj) where xi, yj are in {+/-A, +/-3A, ...}
265
- Symbol Energy: Ei = xi^2 + yj^2
266
- Average Energy: E_avg = (1/M) * Sum(Ei for all points)
267
  General Formula: E_avg = (2/3) * (M - 1) * A^2
268
 
269
  Returns:
270
  tuple: A tuple containing:
271
- - str: A question asking for the average energy of an M-QAM constellation.
272
- - str: A step-by-step solution showing the calculation.
273
  """
274
  # 1. Parameterize the inputs with random values
275
  precision = 2
276
  # Add 256-QAM to the list of possible modulation orders
277
  M = random.choice([4, 16, 64, 256])
278
- # Randomly decide whether A is an integer or a float for more variety
 
279
  if random.choice([True, False]):
280
- # Generate an integer A
281
  A = random.randint(1, 10)
282
  else:
283
- # Generate a float A from a wider range
284
  A = round(random.uniform(0.2, 10.0), precision)
285
 
286
  # 2. Perform the core calculation based on M
287
 
288
  # The general formula for average energy in a square M-QAM is (2/3)(M-1)A^2
289
- # We will calculate it directly for the solution steps.
290
  avg_energy_coeff = (2/3) * (M - 1)
291
  avg_energy = avg_energy_coeff * (A**2)
292
 
@@ -315,27 +300,39 @@ def template_average_energy_mqam():
315
 
316
  f"**Step 3:** Calculate Average Energy (E_avg)\n"
317
  f"We sum the energies of all points and divide by M.\n"
318
- f"Total Energy = 4*(2A^2) + 8*(10A^2) + 4*(18A^2) = 8A^2 + 80A^2 + 72A^2 = 160A^2\n"
319
  f"E_avg = (Total Energy) / M = (160 * A^2) / 16 = 10 * A^2\n"
320
  )
321
 
322
- else: # M == 64
323
  coordinate_set = "{+/-A, +/-3A, +/-5A, +/-7A}"
324
- total_energy_coeff = 64 * avg_energy_coeff
325
  solution_steps = (
326
  "**Step 2:** List Signal Point Energies\n"
327
  "For 64-QAM, there are many groups of points with the same energy. We list a few examples:\n"
328
- "The 4 innermost points at (+/-A, +/-A) have energy E = A^2 + A^2 = 2A^2.\n"
329
- "The 8 points at (+/-A, +/-3A) or (+/-3A, +/-A) have energy E = A^2 + (3A)^2 = 10A^2.\n"
330
- "The 4 outermost points at (+/-7A, +/-7A) have energy E = (7A)^2 + (7A)^2 = 98A^2.\n"
331
  "This process is continued for all 64 points.\n\n"
332
 
333
  "**Step 3:** Calculate Average Energy (E_avg)\n"
334
- "Summing the energies for all 64 points and dividing by M gives the average. We can also use the general formula for square M-QAM: E_avg = (2/3)*(M-1)*A^2.\n"
335
  "E_avg = (2/3) * (64 - 1) * A^2\n"
336
  "E_avg = (2/3) * 63 * A^2 = 42 * A^2\n"
337
  )
338
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  # 3. Generate the question and solution strings
340
 
341
  question = (
@@ -347,7 +344,8 @@ def template_average_energy_mqam():
347
  solution = (
348
  f"**Given Information:**\n"
349
  f"Modulation Scheme: {M}-QAM\n"
350
- f"Distance Parameter (A): {A}\n\n"
 
351
 
352
  f"**Step 1:** Understand the Constellation Structure\n"
353
  f"The constellation is a square grid where coordinates are odd multiples of A. The energy of any point (x, y) is simply x^2 + y^2.\n\n"
@@ -540,7 +538,7 @@ def template_null_to_null_bandwidth():
540
  symbol_rate_rs = bit_rate_hz / k
541
  bandwidth_hz = 2 * symbol_rate_rs
542
 
543
- # --- Start: Inline formatting for bandwidth_hz ---
544
  prefixes = {12: 'T', 9: 'G', 6: 'M', 3: 'k', 0: ''}
545
  if bandwidth_hz > 0:
546
  exponent_b = int(math.floor(math.log10(abs(bandwidth_hz)) / 3.0) * 3)
@@ -549,9 +547,9 @@ def template_null_to_null_bandwidth():
549
  bandwidth_str = f"{round(scaled_b, precision)} {prefix_b}Hz"
550
  else:
551
  bandwidth_str = "0 Hz"
552
- # --- End: Inline formatting ---
553
 
554
- # --- Start: Inline formatting for symbol_rate_rs ---
555
  if symbol_rate_rs > 0:
556
  exponent_rs = int(math.floor(math.log10(abs(symbol_rate_rs)) / 3.0) * 3)
557
  prefix_rs = prefixes.get(exponent_rs, '')
@@ -559,7 +557,7 @@ def template_null_to_null_bandwidth():
559
  symbol_rate_str = f"{round(scaled_rs, precision)} {prefix_rs}symbols/s"
560
  else:
561
  symbol_rate_str = "0 symbols/s"
562
- # --- End: Inline formatting ---
563
 
564
 
565
  # 3. Generate the question and solution strings
 
39
  k = random.randint(2, 20)
40
  carrier_freq_hz = k / bit_duration_s
41
 
42
+ # Inline formatting for bit_duration_s
43
  prefixes = {6: 'M', 3: 'k', 0: '', -3: 'm', -6: 'u', -9: 'n'}
44
  exponent_td = int(math.floor(math.log10(abs(bit_duration_s)) / 3.0) * 3)
45
  prefix_td = prefixes.get(exponent_td, '')
46
  scaled_td = bit_duration_s / (10**exponent_td)
47
  bit_duration_str = f"{round(scaled_td, precision)} {prefix_td}s"
 
48
 
49
+ # Inline formatting for carrier_freq_hz
50
  exponent_fc = int(math.floor(math.log10(abs(carrier_freq_hz)) / 3.0) * 3)
51
  prefix_fc = prefixes.get(exponent_fc, '')
52
  scaled_fc = carrier_freq_hz / (10**exponent_fc)
53
  carrier_freq_str = f"{round(scaled_fc, precision)} {prefix_fc}Hz"
 
54
 
55
  # 2. Perform the core calculation
56
 
 
60
  # Calculate the amplitude of the basis function: sqrt(2 / Tb)
61
  basis_amplitude = math.sqrt(2 / bit_duration_s)
62
 
63
+ # Inline formatting for energy_joules
64
  exponent_e = int(math.floor(math.log10(abs(energy_joules)) / 3.0) * 3)
65
  prefix_e = prefixes.get(exponent_e, '')
66
  scaled_e = energy_joules / (10**exponent_e)
67
  energy_str = f"{round(scaled_e, precision)} {prefix_e}J"
 
68
 
69
  # 3. Generate the question and solution strings
70
 
 
123
  Scenario:
124
  This template tests the ability to represent signals as vectors in a signal
125
  space and calculate the Euclidean distance between them. This distance is a
126
+ key factor in determining the noise immunity of a modulation scheme.
 
 
 
127
 
128
  Core Equations:
129
+ For BPSK (antipodal): d = 2 * sqrt(Eb)
130
+ For BFSK (orthogonal): d = sqrt(2 * Eb)
 
 
 
 
 
 
 
131
 
132
  Returns:
133
  tuple: A tuple containing:
 
142
  # Generate energy per bit, Eb, in a range from picojoules to nanojoules
143
  energy_joules = random.uniform(1e-12, 1e-9)
144
 
145
+ # Inline formatting for energy_joules
146
  prefixes = {0: '', -3: 'm', -6: 'u', -9: 'n', -12: 'p'}
147
  if energy_joules > 0:
148
  exponent_e = int(math.floor(math.log10(abs(energy_joules)) / 3.0) * 3)
149
  prefix_e = prefixes.get(exponent_e, '')
150
  scaled_e = energy_joules / (10**exponent_e)
151
+ energy_str = f"{scaled_e:.{precision}f} {prefix_e}J"
152
  else:
153
  energy_str = "0 J"
 
154
 
155
  # 2. Perform the core calculation based on modulation type
156
 
157
  sqrt_eb = math.sqrt(energy_joules)
158
 
159
+ # Use scientific notation formatting for small numbers instead of round()
160
+ sqrt_eb_fmt = f"{sqrt_eb:.{precision}e}"
161
+
162
  if modulation_type == 'BPSK':
163
  # BPSK signals are antipodal (180 degrees apart)
164
  constellation_type = "antipodal"
165
  dimension = "one-dimensional"
166
  basis_count = "one basis function, psi_1(t)"
167
 
168
+ s1_str = f"({sqrt_eb_fmt})"
169
+ s2_str = f"(-{sqrt_eb_fmt})"
170
 
171
  distance = 2 * sqrt_eb
172
+ dist_fmt = f"{distance:.{precision}e}"
173
 
174
  derivation_steps = (
175
  f"The signal vectors are s1 = (sqrt(Eb)) and s2 = (-sqrt(Eb)).\n"
176
+ f"s1 = ({sqrt_eb_fmt})\n"
177
+ f"s2 = (-{sqrt_eb_fmt})\n\n"
178
+
179
 
180
  f"**Step 2:** Calculate Euclidean Distance (d)\n"
181
  f"The distance d is the magnitude of the difference vector (s1 - s2).\n"
182
  f"s1 - s2 = (sqrt(Eb) - (-sqrt(Eb))) = (2*sqrt(Eb))\n"
183
  f"d = ||s1 - s2|| = 2*sqrt(Eb)\n"
184
+ f"d = 2 * {sqrt_eb_fmt}\n"
185
+ f"d = {dist_fmt}"
186
  )
187
 
188
  else: # Orthogonal BFSK
 
191
  dimension = "two-dimensional"
192
  basis_count = "two basis functions, psi_1(t) and psi_2(t)"
193
 
194
+ s1_str = f"({sqrt_eb_fmt}, 0)"
195
+ s2_str = f"(0, {sqrt_eb_fmt})"
196
 
197
  distance = math.sqrt(2 * energy_joules)
198
+ dist_fmt = f"{distance:.{precision}e}"
199
 
200
  derivation_steps = (
201
  f"The signal vectors are s1 = (sqrt(Eb), 0) and s2 = (0, sqrt(Eb)).\n"
202
+ f"s1 = ({sqrt_eb_fmt}, 0)\n"
203
+ f"s2 = (0, {sqrt_eb_fmt})\n\n"
204
+
205
 
206
  f"**Step 2: ** Calculate Euclidean Distance (d)\n"
207
  f"The distance d is the magnitude of the difference vector (s1 - s2).\n"
208
  f"s1 - s2 = (sqrt(Eb) - 0, 0 - sqrt(Eb)) = (sqrt(Eb), -sqrt(Eb))\n"
209
  f"d = ||s1 - s2|| = sqrt( (sqrt(Eb))^2 + (-sqrt(Eb))^2 ) = sqrt(2*Eb)\n"
210
  f"d = sqrt(2 * {energy_joules:.{precision}e})\n"
211
+ f"d = {dist_fmt}"
212
  )
213
 
214
  # 3. Generate the question and solution strings
 
228
 
229
  f"**Step 1:** Define Signal Vectors\n"
230
  f"For {modulation_type} modulation, the signals are {constellation_type}. This means we can represent them in a {dimension} signal space using {basis_count}.\n"
231
+ f"First, we calculate sqrt(Eb) = sqrt({energy_joules:.{precision}e}) = {sqrt_eb_fmt}.\n"
232
  f"{derivation_steps}\n\n"
233
 
234
  f"**Answer:**\n"
235
  f"a) The signal constellation points are:\n"
236
  f"s1 = {s1_str}\n"
237
  f"s2 = {s2_str}\n"
238
+ f"b) The Euclidean distance between the points is {dist_fmt}."
239
  )
240
 
241
  return question, solution
 
247
  Average Energy of an M-QAM Constellation
248
 
249
  Scenario:
 
 
 
250
  This template tests the ability to calculate the average symbol energy by
251
  analyzing the geometry of the constellation.
252
 
253
  Core Equations:
 
 
 
254
  General Formula: E_avg = (2/3) * (M - 1) * A^2
255
 
256
  Returns:
257
  tuple: A tuple containing:
258
+ - str: A question asking for the average energy.
259
+ - str: A step-by-step solution.
260
  """
261
  # 1. Parameterize the inputs with random values
262
  precision = 2
263
  # Add 256-QAM to the list of possible modulation orders
264
  M = random.choice([4, 16, 64, 256])
265
+
266
+ # Randomly decide whether A is an integer or a float
267
  if random.choice([True, False]):
 
268
  A = random.randint(1, 10)
269
  else:
 
270
  A = round(random.uniform(0.2, 10.0), precision)
271
 
272
  # 2. Perform the core calculation based on M
273
 
274
  # The general formula for average energy in a square M-QAM is (2/3)(M-1)A^2
 
275
  avg_energy_coeff = (2/3) * (M - 1)
276
  avg_energy = avg_energy_coeff * (A**2)
277
 
 
300
 
301
  f"**Step 3:** Calculate Average Energy (E_avg)\n"
302
  f"We sum the energies of all points and divide by M.\n"
303
+ f"Total Energy = 4*(2A^2) + 8*(10A^2) + 4*(18A^2) = 160A^2\n"
304
  f"E_avg = (Total Energy) / M = (160 * A^2) / 16 = 10 * A^2\n"
305
  )
306
 
307
+ elif M == 64:
308
  coordinate_set = "{+/-A, +/-3A, +/-5A, +/-7A}"
 
309
  solution_steps = (
310
  "**Step 2:** List Signal Point Energies\n"
311
  "For 64-QAM, there are many groups of points with the same energy. We list a few examples:\n"
312
+ "- The 4 innermost points at (+/-A, +/-A) have energy E = 2A^2.\n"
313
+ "- The 4 outermost points at (+/-7A, +/-7A) have energy E = (7A)^2 + (7A)^2 = 98A^2.\n"
 
314
  "This process is continued for all 64 points.\n\n"
315
 
316
  "**Step 3:** Calculate Average Energy (E_avg)\n"
317
+ "Summing the energies for all 64 points and dividing by M gives the average. We use the general formula for square M-QAM: E_avg = (2/3)*(M-1)*A^2.\n"
318
  "E_avg = (2/3) * (64 - 1) * A^2\n"
319
  "E_avg = (2/3) * 63 * A^2 = 42 * A^2\n"
320
  )
321
 
322
+ else: # M == 256 (Explicit handling for 256-QAM)
323
+ coordinate_set = "{+/-A, +/-3A, ..., +/-15A}"
324
+ solution_steps = (
325
+ "**Step 2:** List Signal Point Energies\n"
326
+ "For 256-QAM, the grid extends from -15A to +15A. Calculating individual point energies is tedious, so we rely on the general formula derived from the sum of squares.\n"
327
+ "- Innermost points: (+/-A, +/-A)\n"
328
+ "- Outermost points: (+/-15A, +/-15A)\n\n"
329
+
330
+ "**Step 3:** Calculate Average Energy (E_avg)\n"
331
+ "Using the general formula for square M-QAM: E_avg = (2/3)*(M-1)*A^2.\n"
332
+ "E_avg = (2/3) * (256 - 1) * A^2\n"
333
+ "E_avg = (2/3) * 255 * A^2 = 170 * A^2\n"
334
+ )
335
+
336
  # 3. Generate the question and solution strings
337
 
338
  question = (
 
344
  solution = (
345
  f"**Given Information:**\n"
346
  f"Modulation Scheme: {M}-QAM\n"
347
+ f"Distance Parameter (A): {A}\n"
348
+ f"\n\n"
349
 
350
  f"**Step 1:** Understand the Constellation Structure\n"
351
  f"The constellation is a square grid where coordinates are odd multiples of A. The energy of any point (x, y) is simply x^2 + y^2.\n\n"
 
538
  symbol_rate_rs = bit_rate_hz / k
539
  bandwidth_hz = 2 * symbol_rate_rs
540
 
541
+ # Start: Inline formatting for bandwidth_hz
542
  prefixes = {12: 'T', 9: 'G', 6: 'M', 3: 'k', 0: ''}
543
  if bandwidth_hz > 0:
544
  exponent_b = int(math.floor(math.log10(abs(bandwidth_hz)) / 3.0) * 3)
 
547
  bandwidth_str = f"{round(scaled_b, precision)} {prefix_b}Hz"
548
  else:
549
  bandwidth_str = "0 Hz"
550
+ # End: Inline formatting
551
 
552
+ # Start: Inline formatting for symbol_rate_rs
553
  if symbol_rate_rs > 0:
554
  exponent_rs = int(math.floor(math.log10(abs(symbol_rate_rs)) / 3.0) * 3)
555
  prefix_rs = prefixes.get(exponent_rs, '')
 
557
  symbol_rate_str = f"{round(scaled_rs, precision)} {prefix_rs}symbols/s"
558
  else:
559
  symbol_rate_str = "0 symbols/s"
560
+ # End: Inline formatting
561
 
562
 
563
  # 3. Generate the question and solution strings
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/electrostatics.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/electrostatics.cpython-311.pyc and b/data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/electrostatics.cpython-311.pyc differ
 
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/magnetostatics.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/magnetostatics.cpython-311.pyc and b/data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/magnetostatics.cpython-311.pyc differ
 
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/waves_and_phasors.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/waves_and_phasors.cpython-311.pyc and b/data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/waves_and_phasors.cpython-311.pyc differ
 
data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/continuous_time_signals.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/continuous_time_signals.cpython-311.pyc and b/data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/continuous_time_signals.cpython-311.pyc differ
 
data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/discrete_time_signals.cpython-311.pyc CHANGED
Binary files a/data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/discrete_time_signals.cpython-311.pyc and b/data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/discrete_time_signals.cpython-311.pyc differ
 
data/templates/branches/electrical_engineering/signals_and_systems/discrete_time_signals.py CHANGED
@@ -272,37 +272,38 @@ def template_finite_convolution():
272
  Scenario:
273
  This template tests the direct application of the convolution sum, which is
274
  the fundamental operation for determining the output of a Linear
275
- Time-Invariant (LTI) system. It requires convolving two finite-length
276
- sequences, representing an input signal and a system's impulse response.
277
 
278
  Core Equations:
279
  1. Convolution Sum: y[n] = sum(x[k] * h[n-k]) for all k
280
 
281
  Returns:
282
  tuple: A tuple containing:
283
- - str: A question asking to find the output of an LTI system for a given input.
284
  - str: A step-by-step solution demonstrating the convolution process.
285
  """
286
  # 1. Parameterize the inputs with random values
287
 
288
- # Generate sequence x[n] as a dictionary {index: value}
289
  x_len = random.randint(3, 4)
290
  x_origin_pos = random.randint(0, x_len - 1)
291
  x_start_idx = -x_origin_pos
292
  x_n = {x_start_idx + i: random.randint(-3, 3) for i in range(x_len)}
 
293
  # Ensure the sequence isn't all zeros
294
  if all(v == 0 for v in x_n.values()):
295
  x_n[random.choice(list(x_n.keys()))] = random.randint(1, 3)
296
 
297
- # Generate sequence h[n] as a dictionary {index: value}
298
  h_len = random.randint(3, 4)
299
  h_origin_pos = random.randint(0, h_len - 1)
300
  h_start_idx = -h_origin_pos
301
  h_n = {h_start_idx + i: random.randint(-2, 2) for i in range(h_len)}
 
302
  if all(v == 0 for v in h_n.values()):
303
  h_n[random.choice(list(h_n.keys()))] = random.randint(1, 2)
304
 
305
- # Inlined logic to format x_n into a string
306
  min_idx_x = min(x_n.keys())
307
  max_idx_x = max(x_n.keys())
308
  x_parts = []
@@ -311,7 +312,7 @@ def template_finite_convolution():
311
  x_parts.append(f"*{val}*" if i == 0 else str(val))
312
  x_n_str = f"{{{', '.join(x_parts)}}}"
313
 
314
- # Inlined logic to format h_n into a string
315
  min_idx_h = min(h_n.keys())
316
  max_idx_h = max(h_n.keys())
317
  h_parts = []
@@ -322,10 +323,9 @@ def template_finite_convolution():
322
 
323
 
324
  # 2. Perform the core calculation (Convolution)
325
-
326
  y_n = {}
327
- y_start_idx = min(x_n.keys()) + min(h_n.keys())
328
- y_end_idx = max(x_n.keys()) + max(h_n.keys())
329
 
330
  all_k_indices = sorted(list(x_n.keys()))
331
 
@@ -335,7 +335,7 @@ def template_finite_convolution():
335
  x_k = x_n.get(k, 0)
336
  h_nk = h_n.get(n - k, 0)
337
  current_sum += x_k * h_nk
338
- # Only store non-zero values, but the final string will show zeros in the range
339
  if current_sum != 0:
340
  y_n[n] = current_sum
341
 
@@ -350,9 +350,8 @@ def template_finite_convolution():
350
  calculation_steps = []
351
  y_indices_to_show = sorted(y_n.keys()) if y_n else [y_start_idx]
352
 
353
- # To avoid too much output, only show a few sample calculations
354
  if len(y_indices_to_show) > 3:
355
- # Show first, middle, and last non-zero points
356
  middle_index = y_indices_to_show[len(y_indices_to_show)//2]
357
  y_indices_to_show = [y_indices_to_show[0], middle_index, y_indices_to_show[-1]]
358
 
@@ -363,28 +362,27 @@ def template_finite_convolution():
363
  sum_expr_terms = []
364
  val_expr_terms = []
365
 
366
- # Determine the range of k where overlap can occur for clarity
367
- k_min = min(x_n.keys())
368
- k_max = max(x_n.keys())
369
-
370
- for k in range(k_min, k_max + 1):
371
- x_val = x_n.get(k, 0)
372
- # Only show terms where x[k] contributes to the expression
373
- if x_val != 0:
374
- h_val = h_n.get(n - k, 0)
375
- sum_expr_terms.append(f"x[{k}]h[{n-k}]")
376
- val_expr_terms.append(f"({x_val})({h_val})")
377
 
378
  step_str += f"y[{n}] = {' + '.join(sum_expr_terms)}\n"
379
  step_str += f"y[{n}] = {' + '.join(val_expr_terms)}\n"
380
  step_str += f"y[{n}] = {y_n.get(n, 0)}\n"
381
  calculation_steps.append(step_str)
382
 
383
- # Inlined logic to format the final sequence y_n
384
  if not y_n:
385
  y_n_str = "{*0*}"
386
  else:
387
- # The full range of y[n] must be shown, including zeros between start and end
388
  min_idx_y = y_start_idx
389
  max_idx_y = y_end_idx
390
  y_parts = []
@@ -408,12 +406,13 @@ def template_finite_convolution():
408
  f"y[n] = sum over all k of (x[k] * h[n-k])\n\n"
409
 
410
  f"**Step 2:** Apply the Flip-and-Slide Method\n"
411
- f"We can visualize this process by flipping the impulse response h[k] to get h[-k], and then sliding it by 'n' positions. For each slide 'n', we calculate the sum of the products of the overlapping samples.\n\n"
412
- f"Let's calculate a few points:\n\n"
 
413
  f"{calculation_steps_str}\n"
414
 
415
  f"**Step 3: Calculate All Output Values**\n"
416
- f"By continuing this process for all values of 'n' where the sequences could overlap (from n={y_start_idx} to n={y_end_idx}), we get the following values for the output:\n"
417
  f"{y_values_str}\n\n"
418
 
419
  f"**Answer:**\n"
@@ -431,8 +430,7 @@ def template_system_property_linearity():
431
  Scenario:
432
  This template tests the ability to formally prove or disprove if a system
433
  is linear by checking the two defining properties: additivity and
434
- homogeneity (also known as scaling). This is a fundamental concept in
435
- characterizing systems.
436
 
437
  Core Definitions:
438
  1. Additivity: T{x1[n] + x2[n]} = T{x1[n]} + T{x2[n]}
@@ -443,16 +441,19 @@ def template_system_property_linearity():
443
  - str: A question asking to determine if a system is linear.
444
  - str: A step-by-step solution showing the formal proof.
445
  """
446
- # 1. Parameterize the inputs by generating a random system type and equation
447
 
448
  system_type = random.choice(['linear_gain', 'linear_delay', 'nonlinear_offset', 'nonlinear_power'])
449
 
450
- # These variables will be populated based on the system type
451
  equation_str = ""
452
  is_linear = False
453
 
454
- # Strings for each step of the proof
455
- add_y1y2_sum_str = ""
 
 
 
 
456
  add_y3_str = ""
457
  add_comparison_str = ""
458
 
@@ -460,35 +461,41 @@ def template_system_property_linearity():
460
  hom_ya_str = ""
461
  hom_comparison_str = ""
462
 
463
-
464
  if system_type == 'linear_gain':
465
  is_linear = True
466
  k = random.randint(1, 10)
467
  equation_str = f"{k} * x[n]"
468
 
469
- # Additivity Proof Strings
470
- add_y1y2_sum_str = f"y1[n] + y2[n] = ({k} * x1[n]) + ({k} * x2[n]) = {k} * (x1[n] + x2[n])"
471
- add_y3_str = f"y3[n] = {k} * (x3[n]) = {k} * (x1[n] + x2[n])"
 
 
 
472
  add_comparison_str = "Since y3[n] is equal to y1[n] + y2[n], the system satisfies the additivity property."
473
 
474
- # Homogeneity Proof Strings
475
- hom_ay1_str = f"a * y1[n] = a * ({k} * x1[n])"
476
- hom_ya_str = f"ya[n] = {k} * (xa[n]) = {k} * (a * x1[n])"
477
  hom_comparison_str = "Since ya[n] is equal to a * y1[n], the system satisfies the homogeneity property."
478
 
479
  elif system_type == 'linear_delay':
480
  is_linear = True
481
  d = random.randint(1, 10)
482
  equation_str = f"x[n - {d}]"
 
 
 
 
483
 
484
- # Additivity Proof Strings
485
- add_y1y2_sum_str = f"y1[n] + y2[n] = x1[n - {d}] + x2[n - {d}]"
486
- add_y3_str = f"y3[n] = x3[n - {d}] = x1[n - {d}] + x2[n - {d}]"
487
  add_comparison_str = "Since y3[n] is equal to y1[n] + y2[n], the system satisfies the additivity property."
488
 
489
- # Homogeneity Proof Strings
490
- hom_ay1_str = f"a * y1[n] = a * x1[n - {d}]"
491
- hom_ya_str = f"ya[n] = xa[n - {d}] = a * x1[n - {d}]"
492
  hom_comparison_str = "Since ya[n] is equal to a * y1[n], the system satisfies the homogeneity property."
493
 
494
  elif system_type == 'nonlinear_offset':
@@ -497,30 +504,36 @@ def template_system_property_linearity():
497
  C_str = f"+ {C}" if C > 0 else f"- {abs(C)}"
498
  equation_str = f"x[n] {C_str}"
499
 
500
- # Additivity Proof Strings
501
- add_y1y2_sum_str = f"y1[n] + y2[n] = (x1[n] {C_str}) + (x2[n] {C_str}) = x1[n] + x2[n] + {2*C}"
502
- add_y3_str = f"y3[n] = (x3[n]) {C_str} = (x1[n] + x2[n]) {C_str}"
 
 
 
503
  add_comparison_str = f"Since x1[n] + x2[n] + {2*C} is not equal to x1[n] + x2[n] {C_str}, the system fails the additivity test."
504
 
505
- # Homogeneity Proof Strings
506
- hom_ay1_str = f"a * y1[n] = a * (x1[n] {C_str}) = a*x1[n] + {C}*a"
507
- hom_ya_str = f"ya[n] = (xa[n]) {C_str} = (a * x1[n]) {C_str}"
508
  hom_comparison_str = f"Since a*x1[n] + {C}*a is not equal to a*x1[n] {C_str} (for a != 1), the system fails the homogeneity test."
509
 
510
  elif system_type == 'nonlinear_power':
511
  is_linear = False
512
  p = random.randint(2, 3)
513
- equation_str = f"(x[n])**{p}"
 
 
 
514
 
515
- # Additivity Proof Strings
516
- add_y1y2_sum_str = f"y1[n] + y2[n] = (x1[n])**{p} + (x2[n])**{p}"
517
- add_y3_str = f"y3[n] = (x3[n])**{p} = (x1[n] + x2[n])**{p}"
518
- add_comparison_str = f"In general, (x1[n] + x2[n])**{p} is not equal to (x1[n])**{p} + (x2[n])**{p}. Therefore, the system fails the additivity test."
519
 
520
- # Homogeneity Proof Strings
521
- hom_ay1_str = f"a * y1[n] = a * (x1[n])**{p}"
522
- hom_ya_str = f"ya[n] = (xa[n])**{p} = (a * x1[n])**{p} = (a**{p}) * (x1[n])**{p}"
523
- hom_comparison_str = f"Since a * (x1[n])**{p} is not equal to (a**{p}) * (x1[n])**{p} (for a != 1), the system fails the homogeneity test."
524
 
525
  # 2. Generate the question and solution strings
526
 
@@ -538,20 +551,21 @@ def template_system_property_linearity():
538
  f"For a system to be linear, it must satisfy two properties:\n"
539
  f"1. **Additivity:** T{{x1[n] + x2[n]}} = T{{x1[n]}} + T{{x2[n]}}\n"
540
  f"2. **Homogeneity (Scaling):** T{{a*x[n]}} = a*T{{x[n]}}\n"
541
- f"We must test both properties.\n\n"
 
542
 
543
  f"**Step 2:** Test for Additivity\n"
544
  f"Let's define two arbitrary inputs, x1[n] and x2[n]. The corresponding outputs are:\n"
545
- f"y1[n] = {equation_str.replace('x[n]', 'x1[n]')}\n"
546
- f"y2[n] = {equation_str.replace('x[n]', 'x2[n]')}\n\n"
547
  f"The sum of these outputs is:\n"
548
- f"y1[n] + y2[n] = {add_y1y2_sum_str}\n\n"
549
  f"Now, let's define a third input x3[n] = x1[n] + x2[n]. The output y3[n] is:\n"
550
  f"y3[n] = {add_y3_str}\n\n"
551
  f"**Comparison:** {add_comparison_str}\n\n"
552
 
553
  f"**Step 3:** Test for Homogeneity (Scaling)\n"
554
- f"Let's define an input x1[n] and a constant 'a'. The output is y1[n] = {equation_str.replace('x[n]', 'x1[n]')}.\n"
555
  f"The scaled output is:\n"
556
  f"a * y1[n] = {hom_ay1_str}\n\n"
557
  f"Now, let's define a new input xa[n] = a * x1[n]. The output ya[n] is:\n"
@@ -574,8 +588,7 @@ def template_impulse_response_from_lccde():
574
  This template tests the ability to find the impulse response h[n] for a
575
  system described by a Linear Constant-Coefficient Difference Equation
576
  (LCCDE). The process involves setting the input to the unit impulse,
577
- delta[n], and solving the resulting recurrence relation for h[n] by finding
578
- initial conditions and then solving the homogeneous equation.
579
 
580
  Core Definitions:
581
  1. Impulse Response: h[n] = T{delta[n]}
@@ -583,7 +596,7 @@ def template_impulse_response_from_lccde():
583
 
584
  Returns:
585
  tuple: A tuple containing:
586
- - str: A question asking for the impulse response of a system given its LCCDE.
587
  - str: A step-by-step solution detailing the recursive method.
588
  """
589
  # 1. Parameterize by randomly choosing system order and coefficients
@@ -622,7 +635,7 @@ def template_impulse_response_from_lccde():
622
  if h1 != 0:
623
  base = -a1
624
  # Use fmt helper to correctly sign the term
625
- h_decay_expr = f" {fmt(h1, '')}({base})**(n-1) * u[n-1]"
626
 
627
  final_h_n = f"{term1_str}{h_decay_expr}"
628
 
@@ -645,11 +658,11 @@ def template_impulse_response_from_lccde():
645
  f"For n >= 2, the input delta terms are zero. The equation becomes homogeneous:\n"
646
  f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] = 0 => h[n] = {-a1}*h[n-1]\n\n"
647
  f"The solution to this recurrence for n >= 1 is a decaying exponential that starts at n=1 with value h[1].\n"
648
- f"This part of the response can be written as h[1]*({-a1})**(n-1)*u[n-1].\n\n"
649
 
650
  f"**Step 5:** Combine Results for the Final Expression\n"
651
  f"The total impulse response is the sum of the value at n=0 and the response for n >= 1:\n"
652
- f"h[n] = h[0]*delta[n] + h[1]*({-a1})**(n-1)*u[n-1]\n"
653
  )
654
 
655
  else: # order == 'second'
@@ -660,14 +673,16 @@ def template_impulse_response_from_lccde():
660
  a1 = random.randint(-6, 6)
661
  if a1 == 0: a1 = 1
662
 
663
- # Randomize RHS for increased diversity
664
  b1 = random.randint(-3, 3) if random.random() > 0.4 else 0
665
- b2 = random.randint(-2, 2) if random.random() > 0.3 else 0
 
 
666
 
667
  # Build equation strings
668
  x_terms = f"{b0}x[n]"
669
  if b1 != 0: x_terms += f" {fmt(b1, 'x[n-1]')}x[n-1]"
670
- if b2 != 0: x_terms += f" {fmt(b2, 'x[n-2]')}x[n-2]"
671
  equation_str = f"y[n] {fmt(a1, 'y[n-1]')}y[n-1] {fmt(a2, 'y[n-2]')}y[n-2] = {x_terms}"
672
 
673
  # 2. Perform the core calculation for a second-order system
@@ -687,13 +702,12 @@ def template_impulse_response_from_lccde():
687
  C2 = (h0 * r1 - h1) / (r1 - r2)
688
  C1_str, C2_str = f"{C1:.2f}", f"{C2:.2f}"
689
 
690
- final_h_n = f"({C1_str}({r1_str})**n {fmt(C2, C2_str)}({r2_str})**n) * u[n]"
691
 
692
  # 3. Generate the solution string for a second-order system
693
  h_eq = f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] {fmt(a2, 'h[n-2]')}h[n-2]"
694
  d_eq = f"{b0}*delta[n]"
695
  if b1 != 0: d_eq += f" {fmt(b1, 'd[n-1]')}*delta[n-1]"
696
- if b2 != 0: d_eq += f" {fmt(b2, 'd[n-2]')}*delta[n-2]"
697
 
698
  solution_steps = (
699
  f"**Step 3:** Solve Recursively for Initial Conditions\n"
@@ -704,16 +718,16 @@ def template_impulse_response_from_lccde():
704
  f"**h[0] = {h0}**\n\n"
705
  f"**For n = 1:**\n"
706
  f"h[1] {fmt(a1, 'h[0]')}h[0] {fmt(a2, 'h[-1]')}h[-1] = ... {fmt(b1, 'd[0]')}*delta[0] ...\n"
707
- f"h[1] {fmt(a1, h0)}*({h0}) {fmt(a2, '0')}*(0) = {b0}*(0) + {b1}*(1) + {b2}*(0)\n"
708
  f"h[1] = {b1} - {a1*h0}\n"
709
  f"**h[1] = {h1}**\n\n"
710
 
711
  f"**Step 4:** Find the Homogeneous Solution\n"
712
- f"For n >= 3, the input is zero, and the equation is homogeneous:\n"
713
  f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] {fmt(a2, 'h[n-2]')}h[n-2] = 0\n\n"
714
- f"We solve this by finding the roots of the characteristic equation: r**2 {fmt(a1, 'r')}r {fmt(a2, '')} = 0\n"
715
  f"Using the quadratic formula, the roots are r1 = {r1_str}, r2 = {r2_str}.\n"
716
- f"The general solution for n >= 0 is h[n] = C1*({r1_str})**n + C2*({r2_str})**n.\n\n"
717
 
718
  f"**Step 5:** Use Initial Conditions to Find Coefficients\n"
719
  f"We use h[0] and h[1] to create a system of two equations:\n"
@@ -734,7 +748,8 @@ def template_impulse_response_from_lccde():
734
  f"The system equation is {equation_str}\n\n"
735
 
736
  f"**Step 1:** Set Input to the Unit Impulse\n"
737
- f"By definition, the impulse response h[n] is the output y[n] when the input x[n] is the unit impulse, delta[n].\n\n"
 
738
 
739
  f"**Step 2:** Substitute h[n] and delta[n] into the Equation\n"
740
  f"Replacing y[n] with h[n] and x[n] with delta[n], we get:\n"
 
272
  Scenario:
273
  This template tests the direct application of the convolution sum, which is
274
  the fundamental operation for determining the output of a Linear
275
+ Time-Invariant (LTI) system.
 
276
 
277
  Core Equations:
278
  1. Convolution Sum: y[n] = sum(x[k] * h[n-k]) for all k
279
 
280
  Returns:
281
  tuple: A tuple containing:
282
+ - str: A question asking to find the output of an LTI system.
283
  - str: A step-by-step solution demonstrating the convolution process.
284
  """
285
  # 1. Parameterize the inputs with random values
286
 
287
+ # Generate sequence x[n]
288
  x_len = random.randint(3, 4)
289
  x_origin_pos = random.randint(0, x_len - 1)
290
  x_start_idx = -x_origin_pos
291
  x_n = {x_start_idx + i: random.randint(-3, 3) for i in range(x_len)}
292
+
293
  # Ensure the sequence isn't all zeros
294
  if all(v == 0 for v in x_n.values()):
295
  x_n[random.choice(list(x_n.keys()))] = random.randint(1, 3)
296
 
297
+ # Generate sequence h[n]
298
  h_len = random.randint(3, 4)
299
  h_origin_pos = random.randint(0, h_len - 1)
300
  h_start_idx = -h_origin_pos
301
  h_n = {h_start_idx + i: random.randint(-2, 2) for i in range(h_len)}
302
+
303
  if all(v == 0 for v in h_n.values()):
304
  h_n[random.choice(list(h_n.keys()))] = random.randint(1, 2)
305
 
306
+ # Format x[n] string
307
  min_idx_x = min(x_n.keys())
308
  max_idx_x = max(x_n.keys())
309
  x_parts = []
 
312
  x_parts.append(f"*{val}*" if i == 0 else str(val))
313
  x_n_str = f"{{{', '.join(x_parts)}}}"
314
 
315
+ # Format h[n] string
316
  min_idx_h = min(h_n.keys())
317
  max_idx_h = max(h_n.keys())
318
  h_parts = []
 
323
 
324
 
325
  # 2. Perform the core calculation (Convolution)
 
326
  y_n = {}
327
+ y_start_idx = min_idx_x + min_idx_h
328
+ y_end_idx = max_idx_x + max_idx_h
329
 
330
  all_k_indices = sorted(list(x_n.keys()))
331
 
 
335
  x_k = x_n.get(k, 0)
336
  h_nk = h_n.get(n - k, 0)
337
  current_sum += x_k * h_nk
338
+ # Store non-zero values (or boundary zeros if needed, but dict sparse is fine)
339
  if current_sum != 0:
340
  y_n[n] = current_sum
341
 
 
350
  calculation_steps = []
351
  y_indices_to_show = sorted(y_n.keys()) if y_n else [y_start_idx]
352
 
353
+ # To avoid too much output, show first, middle, and last
354
  if len(y_indices_to_show) > 3:
 
355
  middle_index = y_indices_to_show[len(y_indices_to_show)//2]
356
  y_indices_to_show = [y_indices_to_show[0], middle_index, y_indices_to_show[-1]]
357
 
 
362
  sum_expr_terms = []
363
  val_expr_terms = []
364
 
365
+ # Iterate over ALL valid k in x[n] to show full expansion, ensuring consistency
366
+ # Removed the "if x_val != 0" check to show all terms explicitly
367
+ for k in sorted(x_n.keys()):
368
+ x_val = x_n[k]
369
+ h_val = h_n.get(n - k, 0)
370
+
371
+ # Identify h index for clarity
372
+ h_idx = n - k
373
+
374
+ sum_expr_terms.append(f"x[{k}]h[{h_idx}]")
375
+ val_expr_terms.append(f"({x_val})({h_val})")
376
 
377
  step_str += f"y[{n}] = {' + '.join(sum_expr_terms)}\n"
378
  step_str += f"y[{n}] = {' + '.join(val_expr_terms)}\n"
379
  step_str += f"y[{n}] = {y_n.get(n, 0)}\n"
380
  calculation_steps.append(step_str)
381
 
382
+ # Format the final sequence y_n
383
  if not y_n:
384
  y_n_str = "{*0*}"
385
  else:
 
386
  min_idx_y = y_start_idx
387
  max_idx_y = y_end_idx
388
  y_parts = []
 
406
  f"y[n] = sum over all k of (x[k] * h[n-k])\n\n"
407
 
408
  f"**Step 2:** Apply the Flip-and-Slide Method\n"
409
+ f"We can visualize this process by flipping the impulse response h[k] to get h[-k], and then sliding it by 'n' positions. For each slide 'n', we calculate the sum of the products of the overlapping samples.\n"
410
+ f"\n\n"
411
+ f"Let's calculate a few points explicitly:\n\n"
412
  f"{calculation_steps_str}\n"
413
 
414
  f"**Step 3: Calculate All Output Values**\n"
415
+ f"By continuing this process for all values of 'n' where the sequences overlap (from n={y_start_idx} to n={y_end_idx}), we get the full output sequence:\n"
416
  f"{y_values_str}\n\n"
417
 
418
  f"**Answer:**\n"
 
430
  Scenario:
431
  This template tests the ability to formally prove or disprove if a system
432
  is linear by checking the two defining properties: additivity and
433
+ homogeneity (scaling).
 
434
 
435
  Core Definitions:
436
  1. Additivity: T{x1[n] + x2[n]} = T{x1[n]} + T{x2[n]}
 
441
  - str: A question asking to determine if a system is linear.
442
  - str: A step-by-step solution showing the formal proof.
443
  """
444
+ # 1. Parameterize the inputs
445
 
446
  system_type = random.choice(['linear_gain', 'linear_delay', 'nonlinear_offset', 'nonlinear_power'])
447
 
 
448
  equation_str = ""
449
  is_linear = False
450
 
451
+ # We will define these explicitly for each case to avoid "replace" bugs
452
+ y1_str = ""
453
+ y2_str = ""
454
+
455
+ # Strings for the RHS of the proof steps (excluding "y = " prefix)
456
+ add_sum_str = ""
457
  add_y3_str = ""
458
  add_comparison_str = ""
459
 
 
461
  hom_ya_str = ""
462
  hom_comparison_str = ""
463
 
 
464
  if system_type == 'linear_gain':
465
  is_linear = True
466
  k = random.randint(1, 10)
467
  equation_str = f"{k} * x[n]"
468
 
469
+ y1_str = f"{k} * x1[n]"
470
+ y2_str = f"{k} * x2[n]"
471
+
472
+ # Additivity
473
+ add_sum_str = f"({k} * x1[n]) + ({k} * x2[n]) = {k} * (x1[n] + x2[n])"
474
+ add_y3_str = f"{k} * (x3[n]) = {k} * (x1[n] + x2[n])"
475
  add_comparison_str = "Since y3[n] is equal to y1[n] + y2[n], the system satisfies the additivity property."
476
 
477
+ # Homogeneity
478
+ hom_ay1_str = f"a * ({k} * x1[n])"
479
+ hom_ya_str = f"{k} * (xa[n]) = {k} * (a * x1[n])"
480
  hom_comparison_str = "Since ya[n] is equal to a * y1[n], the system satisfies the homogeneity property."
481
 
482
  elif system_type == 'linear_delay':
483
  is_linear = True
484
  d = random.randint(1, 10)
485
  equation_str = f"x[n - {d}]"
486
+
487
+ # Explicitly constructing strings handles the 'n-d' index correctly
488
+ y1_str = f"x1[n - {d}]"
489
+ y2_str = f"x2[n - {d}]"
490
 
491
+ # Additivity
492
+ add_sum_str = f"x1[n - {d}] + x2[n - {d}]"
493
+ add_y3_str = f"x3[n - {d}] = x1[n - {d}] + x2[n - {d}]"
494
  add_comparison_str = "Since y3[n] is equal to y1[n] + y2[n], the system satisfies the additivity property."
495
 
496
+ # Homogeneity
497
+ hom_ay1_str = f"a * x1[n - {d}]"
498
+ hom_ya_str = f"xa[n - {d}] = a * x1[n - {d}]"
499
  hom_comparison_str = "Since ya[n] is equal to a * y1[n], the system satisfies the homogeneity property."
500
 
501
  elif system_type == 'nonlinear_offset':
 
504
  C_str = f"+ {C}" if C > 0 else f"- {abs(C)}"
505
  equation_str = f"x[n] {C_str}"
506
 
507
+ y1_str = f"x1[n] {C_str}"
508
+ y2_str = f"x2[n] {C_str}"
509
+
510
+ # Additivity
511
+ add_sum_str = f"(x1[n] {C_str}) + (x2[n] {C_str}) = x1[n] + x2[n] + {2*C}"
512
+ add_y3_str = f"(x3[n]) {C_str} = (x1[n] + x2[n]) {C_str}"
513
  add_comparison_str = f"Since x1[n] + x2[n] + {2*C} is not equal to x1[n] + x2[n] {C_str}, the system fails the additivity test."
514
 
515
+ # Homogeneity
516
+ hom_ay1_str = f"a * (x1[n] {C_str}) = a*x1[n] + {C}*a"
517
+ hom_ya_str = f"(xa[n]) {C_str} = (a * x1[n]) {C_str}"
518
  hom_comparison_str = f"Since a*x1[n] + {C}*a is not equal to a*x1[n] {C_str} (for a != 1), the system fails the homogeneity test."
519
 
520
  elif system_type == 'nonlinear_power':
521
  is_linear = False
522
  p = random.randint(2, 3)
523
+ equation_str = f"(x[n])^{p}"
524
+
525
+ y1_str = f"(x1[n])^{p}"
526
+ y2_str = f"(x2[n])^{p}"
527
 
528
+ # Additivity
529
+ add_sum_str = f"(x1[n])^{p} + (x2[n])^{p}"
530
+ add_y3_str = f"(x3[n])^{p} = (x1[n] + x2[n])^{p}"
531
+ add_comparison_str = f"In general, (x1[n] + x2[n])^{p} is not equal to (x1[n])^{p} + (x2[n])^{p}. Therefore, the system fails the additivity test."
532
 
533
+ # Homogeneity
534
+ hom_ay1_str = f"a * (x1[n])^{p}"
535
+ hom_ya_str = f"(xa[n])^{p} = (a * x1[n])^{p} = (a^{p}) * (x1[n])^{p}"
536
+ hom_comparison_str = f"Since a * (x1[n])^{p} is not equal to (a^{p}) * (x1[n])^{p} (for a != 1), the system fails the homogeneity test."
537
 
538
  # 2. Generate the question and solution strings
539
 
 
551
  f"For a system to be linear, it must satisfy two properties:\n"
552
  f"1. **Additivity:** T{{x1[n] + x2[n]}} = T{{x1[n]}} + T{{x2[n]}}\n"
553
  f"2. **Homogeneity (Scaling):** T{{a*x[n]}} = a*T{{x[n]}}\n"
554
+ f"We must test both properties.\n"
555
+ f"\n\n"
556
 
557
  f"**Step 2:** Test for Additivity\n"
558
  f"Let's define two arbitrary inputs, x1[n] and x2[n]. The corresponding outputs are:\n"
559
+ f"y1[n] = {y1_str}\n"
560
+ f"y2[n] = {y2_str}\n\n"
561
  f"The sum of these outputs is:\n"
562
+ f"y1[n] + y2[n] = {add_sum_str}\n\n"
563
  f"Now, let's define a third input x3[n] = x1[n] + x2[n]. The output y3[n] is:\n"
564
  f"y3[n] = {add_y3_str}\n\n"
565
  f"**Comparison:** {add_comparison_str}\n\n"
566
 
567
  f"**Step 3:** Test for Homogeneity (Scaling)\n"
568
+ f"Let's define an input x1[n] and a constant 'a'. The output is y1[n] = {y1_str}.\n"
569
  f"The scaled output is:\n"
570
  f"a * y1[n] = {hom_ay1_str}\n\n"
571
  f"Now, let's define a new input xa[n] = a * x1[n]. The output ya[n] is:\n"
 
588
  This template tests the ability to find the impulse response h[n] for a
589
  system described by a Linear Constant-Coefficient Difference Equation
590
  (LCCDE). The process involves setting the input to the unit impulse,
591
+ delta[n], and solving the resulting recurrence relation for h[n].
 
592
 
593
  Core Definitions:
594
  1. Impulse Response: h[n] = T{delta[n]}
 
596
 
597
  Returns:
598
  tuple: A tuple containing:
599
+ - str: A question asking for the impulse response.
600
  - str: A step-by-step solution detailing the recursive method.
601
  """
602
  # 1. Parameterize by randomly choosing system order and coefficients
 
635
  if h1 != 0:
636
  base = -a1
637
  # Use fmt helper to correctly sign the term
638
+ h_decay_expr = f" {fmt(h1, '')}({base})^(n-1) * u[n-1]"
639
 
640
  final_h_n = f"{term1_str}{h_decay_expr}"
641
 
 
658
  f"For n >= 2, the input delta terms are zero. The equation becomes homogeneous:\n"
659
  f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] = 0 => h[n] = {-a1}*h[n-1]\n\n"
660
  f"The solution to this recurrence for n >= 1 is a decaying exponential that starts at n=1 with value h[1].\n"
661
+ f"This part of the response can be written as h[1]*({-a1})^(n-1)*u[n-1].\n\n"
662
 
663
  f"**Step 5:** Combine Results for the Final Expression\n"
664
  f"The total impulse response is the sum of the value at n=0 and the response for n >= 1:\n"
665
+ f"h[n] = h[0]*delta[n] + h[1]*({-a1})^(n-1)*u[n-1]\n"
666
  )
667
 
668
  else: # order == 'second'
 
673
  a1 = random.randint(-6, 6)
674
  if a1 == 0: a1 = 1
675
 
676
+ # Randomize RHS
677
  b1 = random.randint(-3, 3) if random.random() > 0.4 else 0
678
+ # Force b2 = 0 to ensure the coefficient fitting method is valid
679
+ # If b2 != 0, there is an impulse at n=2 that breaks the homogeneous assumption for n>=2
680
+ b2 = 0
681
 
682
  # Build equation strings
683
  x_terms = f"{b0}x[n]"
684
  if b1 != 0: x_terms += f" {fmt(b1, 'x[n-1]')}x[n-1]"
685
+ # b2 term removed
686
  equation_str = f"y[n] {fmt(a1, 'y[n-1]')}y[n-1] {fmt(a2, 'y[n-2]')}y[n-2] = {x_terms}"
687
 
688
  # 2. Perform the core calculation for a second-order system
 
702
  C2 = (h0 * r1 - h1) / (r1 - r2)
703
  C1_str, C2_str = f"{C1:.2f}", f"{C2:.2f}"
704
 
705
+ final_h_n = f"({C1_str}({r1_str})^n {fmt(C2, C2_str)}({r2_str})^n) * u[n]"
706
 
707
  # 3. Generate the solution string for a second-order system
708
  h_eq = f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] {fmt(a2, 'h[n-2]')}h[n-2]"
709
  d_eq = f"{b0}*delta[n]"
710
  if b1 != 0: d_eq += f" {fmt(b1, 'd[n-1]')}*delta[n-1]"
 
711
 
712
  solution_steps = (
713
  f"**Step 3:** Solve Recursively for Initial Conditions\n"
 
718
  f"**h[0] = {h0}**\n\n"
719
  f"**For n = 1:**\n"
720
  f"h[1] {fmt(a1, 'h[0]')}h[0] {fmt(a2, 'h[-1]')}h[-1] = ... {fmt(b1, 'd[0]')}*delta[0] ...\n"
721
+ f"h[1] {fmt(a1, h0)}*({h0}) {fmt(a2, '0')}*(0) = {b0}*(0) + {b1}*(1)\n"
722
  f"h[1] = {b1} - {a1*h0}\n"
723
  f"**h[1] = {h1}**\n\n"
724
 
725
  f"**Step 4:** Find the Homogeneous Solution\n"
726
+ f"For n >= 2, the input is zero (since b2=0), so the equation becomes homogeneous:\n"
727
  f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] {fmt(a2, 'h[n-2]')}h[n-2] = 0\n\n"
728
+ f"We solve this by finding the roots of the characteristic equation: r^2 {fmt(a1, 'r')}r {fmt(a2, '')} = 0\n"
729
  f"Using the quadratic formula, the roots are r1 = {r1_str}, r2 = {r2_str}.\n"
730
+ f"The general solution for n >= 0 is h[n] = C1*({r1_str})^n + C2*({r2_str})^n.\n\n"
731
 
732
  f"**Step 5:** Use Initial Conditions to Find Coefficients\n"
733
  f"We use h[0] and h[1] to create a system of two equations:\n"
 
748
  f"The system equation is {equation_str}\n\n"
749
 
750
  f"**Step 1:** Set Input to the Unit Impulse\n"
751
+ f"By definition, the impulse response h[n] is the output y[n] when the input x[n] is the unit impulse, delta[n].\n"
752
+ f"\n\n"
753
 
754
  f"**Step 2:** Substitute h[n] and delta[n] into the Equation\n"
755
  f"Replacing y[n] with h[n] and x[n] with delta[n], we get:\n"
data/templates/branches/mechanical_engineering/__pycache__/constants.cpython-311.pyc ADDED
Binary file (8.66 kB). View file
 
data/templates/branches/mechanical_engineering/fluid_mechanics/__pycache__/fluid_kinematics.cpython-311.pyc ADDED
Binary file (39.8 kB). View file
 
data/templates/branches/mechanical_engineering/fluid_mechanics/__pycache__/fluid_statics.cpython-311.pyc ADDED
Binary file (28.5 kB). View file
 
data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_kinematics.py CHANGED
@@ -166,17 +166,17 @@ def template_volumetric_flow_rate():
166
 
167
  # 2. Perform calculations and generate strings based on the chosen geometry
168
  if geometry == 'pipe':
169
- # --- Pipe-specific parameters ---
170
  diameter_mm = random.randint(20, 200)
171
  radius_m = diameter_mm / 2000.0
172
 
173
- # --- Core calculations for the pipe ---
174
  area = math.pi * radius_m**2
175
  # For a parabolic profile in a pipe, the exact integral yields Q = (1/2) * U_max * A
176
  flow_rate = 0.5 * u_max * area
177
  avg_velocity = u_max / 2.0
178
 
179
- # --- Generate question and solution strings for the pipe ---
180
  question = (
181
  f"The velocity profile for a fluid flowing through a circular pipe with a diameter of {diameter_mm} mm "
182
  f"is given by u(r) = U_max * (1 - (r/R)^2), where R is the radius of the pipe and the maximum "
@@ -221,19 +221,19 @@ def template_volumetric_flow_rate():
221
  )
222
 
223
  else: # geometry == 'channel'
224
- # --- Channel-specific parameters ---
225
  height_cm = random.randint(5, 50)
226
  width_cm = random.randint(10, 100)
227
  height_m = height_cm / 100.0
228
  width_m = width_cm / 100.0
229
 
230
- # --- Core calculations for the channel ---
231
  area = width_m * height_m
232
  # For a linear profile in a channel, the exact integral yields Q = (1/2) * U_max * A
233
  flow_rate = 0.5 * u_max * area
234
  avg_velocity = u_max / 2.0
235
 
236
- # --- Generate question and solution strings for the channel ---
237
  question = (
238
  f"A fluid flows through a rectangular channel that is {width_cm} cm wide and {height_cm} cm high. "
239
  f"The velocity profile is given by u(y) = U_max * (y/H), where H is the channel height and the "
@@ -319,18 +319,18 @@ def template_vorticity_check():
319
 
320
  # 2. Perform calculations and generate strings based on the flow type
321
  if flow_type == '2D':
322
- # --- Define a more complex 2D velocity field ---
323
  # u = A*x*y, v = B*x^2 + C*y^2
324
  # This makes derivatives dependent on the point (x, y).
325
 
326
- # --- Core calculations for 2D flow ---
327
  du_dy_val = A * x_point
328
  dv_dx_val = 2 * B * x_point
329
 
330
  zeta_z = dv_dx_val - du_dy_val
331
  is_irrotational = abs(zeta_z) < 1e-9 # Check for zero with floating point tolerance
332
 
333
- # --- Generate question and solution strings for 2D flow ---
334
  question = (
335
  f"A 2D fluid flow is described by the velocity field:\n"
336
  f"u = {A}xy\n"
@@ -377,11 +377,11 @@ def template_vorticity_check():
377
  F = random.randint(-4, 4)
378
  z_point = round(random.uniform(0.5, 3.0), 1)
379
 
380
- # --- Define a more complex 3D velocity field ---
381
  # u = A*y^2, v = B*z^2 + C*x, w = D*x^2 + E*y
382
  # This allows all components of vorticity to be non-zero and position-dependent.
383
 
384
- # --- Core calculations for 3D flow ---
385
  du_dy_val = 2 * A * y_point
386
  du_dz_val = 0
387
 
@@ -397,7 +397,7 @@ def template_vorticity_check():
397
 
398
  is_irrotational = (abs(zeta_x) < 1e-9 and abs(zeta_y) < 1e-9 and abs(zeta_z) < 1e-9)
399
 
400
- # --- Generate question and solution strings for 3D flow ---
401
  question = (
402
  f"A 3D fluid flow is described by the velocity field:\n"
403
  f"u = {A}y^2\n"
@@ -456,9 +456,7 @@ def template_particle_pathline():
456
  Scenario:
457
  This template distinguishes between the Eulerian description (velocity field)
458
  and the Lagrangian description (individual particle path). It requires solving
459
- a system of ordinary differential equations (ODEs) to trace a particle's
460
- movement from a starting point to its position at a later time. The velocity
461
- field is steady and designed so the variables are separable.
462
 
463
  Core Equations:
464
  Pathline Definition: dx/dt = u(x, y) and dy/dt = v(x, y)
@@ -467,20 +465,22 @@ def template_particle_pathline():
467
  Returns:
468
  tuple: A tuple containing:
469
  - str: A question asking for the particle's final coordinates.
470
- - str: A step-by-step solution showing the derivation of the pathline equations.
471
  """
472
- # 1. Parameterize the inputs with random values
473
- A = random.randint(1, 5)
474
- B = random.randint(1, 5)
 
 
 
475
 
476
- x0 = round(random.uniform(0.5, 4.0), 1)
477
- y0 = round(random.uniform(0.5, 4.0), 1)
478
- tf = round(random.uniform(0.1, 2.0), 2)
479
 
480
- precision = 4
481
 
482
  # 2. Perform the core calculations for the solution
483
- # Solve for the final position by integrating the velocity components
484
  # x(t) = x0 * exp(A*t)
485
  # y(t) = y0 * exp(-B*t)
486
  xf = x0 * math.exp(A * tf)
@@ -488,8 +488,9 @@ def template_particle_pathline():
488
 
489
  # 3. Generate the question and solution strings
490
  question = (
491
- f"A steady, 2D velocity field is given by u = {A}x and v = -{B}y. "
492
- f"A fluid particle is located at the initial position (x0, y0) = ({x0}, {y0}) at time t=0.\n\n"
 
493
  f"Determine the particle's coordinates (x, y) at time t = {tf} s."
494
  )
495
 
@@ -500,8 +501,9 @@ def template_particle_pathline():
500
  f"Final time (tf) = {tf} s\n\n"
501
 
502
  f"**Step 1:** Set up the differential equation for the x-coordinate.\n"
503
- f"The pathline is defined by dx/dt = u.\n"
504
- f" dx/dt = {A}x\n\n"
 
505
 
506
  f"**Step 2:** Solve the ODE for x(t) by separating variables.\n"
507
  f" (1/x) dx = {A} dt\n"
@@ -530,11 +532,11 @@ def template_particle_pathline():
530
 
531
  f"**Step 6:** Calculate the final position at t = {tf} s.\n"
532
  f"Substitute t = {tf} into the pathline equations:\n"
533
- f" x({tf}) = {x0} * exp({A} * {tf}) = {round(xf, precision)}\n"
534
- f" y({tf}) = {y0} * exp(-{B} * {tf}) = {round(yf, precision)}\n\n"
535
 
536
  f"**Answer:**\n"
537
- f"At t = {tf} s, the particle's coordinates are ({round(xf, precision)}, {round(yf, precision)})."
538
  )
539
 
540
  return question, solution
@@ -549,8 +551,7 @@ def template_incompressible_continuity():
549
  This problem uses the differential form of the conservation of mass
550
  (continuity equation) for a 2D, incompressible flow. Given one velocity
551
  component, the user must find the other by performing partial differentiation
552
- and integration. The problem asks for the simplest possible expression,
553
- which implies the constant of integration is zero.
554
 
555
  Core Equation:
556
  2D Incompressible Continuity: du/dx + dv/dy = 0
@@ -560,27 +561,63 @@ def template_incompressible_continuity():
560
  - str: A question asking for the unknown velocity component.
561
  - str: A step-by-step solution showing the derivation.
562
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  # 1. Parameterize the inputs with random values
564
-
565
- # Randomly choose which velocity component is given
566
  given_component = random.choice(['u', 'v'])
567
-
568
- # Random non-zero coefficients for the polynomial terms
569
  A = random.choice([i for i in range(-5, 6) if i != 0])
570
  B = random.choice([i for i in range(-5, 6) if i != 0])
571
 
572
  # 2. Perform calculations and generate strings based on the chosen component
573
  if given_component == 'u':
574
- # --- The u-component is given, find v ---
575
- u_expr = f"{A}x^2 + {B}xy"
 
 
 
 
 
576
 
577
- # Core calculations
578
- # du/dx = 2*A*x + B*y
579
- du_dx_expr = f"{2*A}x + {B}y"
580
- # dv/dy = -du/dx
581
- dv_dy_expr = f"-({du_dx_expr})"
582
- # v = integral(dv/dy) dy = integral(-(2*A*x + B*y)) dy
583
- v_expr = f"{-2*A}xy - {B/2.0}y^2"
584
 
585
  question = (
586
  f"A 2D, steady, incompressible flow has a velocity component in the x-direction "
@@ -592,42 +629,49 @@ def template_incompressible_continuity():
592
  f"**Given:**\n"
593
  f"The flow is 2D, steady, and incompressible.\n"
594
  f"The u-component of velocity is u = {u_expr}\n\n"
595
-
596
  f"**Step 1:** State the 2D incompressible continuity equation.\n"
597
  f"The equation is: du/dx + dv/dy = 0\n\n"
598
-
 
599
  f"**Step 2:** Calculate the partial derivative of the given u-component with respect to x.\n"
600
  f" du/dx = d/dx({u_expr})\n"
601
  f" du/dx = {du_dx_expr}\n\n"
602
-
603
  f"**Step 3:** Rearrange the continuity equation to solve for dv/dy.\n"
604
  f" dv/dy = -du/dx\n"
605
- f" dv/dy = -({du_dx_expr}) = {-2*A}x - {B}y\n\n"
606
 
607
  f"**Step 4:** Integrate dv/dy with respect to y to find v(x, y).\n"
608
- f" v(x, y) = integral({-2*A}x - {B}y) dy\n"
609
- f" v(x, y) = {-2*A}xy - ({B}/2)y^2 + f(x)\n"
610
  f"The 'constant' of integration, f(x), is an arbitrary function of x.\n\n"
611
-
612
  f"**Step 5:** Provide the simplest form for v(x, y).\n"
613
  f"To find the simplest expression, we set the integration constant f(x) to zero.\n"
614
  f" v(x, y) = {v_expr}\n\n"
615
-
616
  f"**Answer:**\n"
617
  f"The simplest expression for the y-component of velocity is v = {v_expr}."
618
  )
619
 
620
  else: # given_component == 'v'
621
- # --- The v-component is given, find u ---
622
- v_expr = f"{A}y^2 + {B}xy"
 
623
 
624
- # Core calculations
625
- # dv/dy = 2*A*y + B*x
626
- dv_dy_expr = f"{2*A}y + {B}x"
627
- # du/dx = -dv/dy
628
- du_dx_expr = f"-({dv_dy_expr})"
629
- # u = integral(du/dx) dx = integral(-(2*A*y + B*x)) dx
630
- u_expr_sol = f"{-2*A}xy - {B/2.0}x^2"
 
 
 
 
 
631
 
632
  question = (
633
  f"A 2D, steady, incompressible flow has a velocity component in the y-direction "
@@ -639,27 +683,27 @@ def template_incompressible_continuity():
639
  f"**Given:**\n"
640
  f"The flow is 2D, steady, and incompressible.\n"
641
  f"The v-component of velocity is v = {v_expr}\n\n"
642
-
643
  f"**Step 1:** State the 2D incompressible continuity equation.\n"
644
  f"The equation is: du/dx + dv/dy = 0\n\n"
645
-
646
  f"**Step 2:** Calculate the partial derivative of the given v-component with respect to y.\n"
647
  f" dv/dy = d/dy({v_expr})\n"
648
  f" dv/dy = {dv_dy_expr}\n\n"
649
-
650
  f"**Step 3:** Rearrange the continuity equation to solve for du/dx.\n"
651
  f" du/dx = -dv/dy\n"
652
- f" du/dx = -({dv_dy_expr}) = {-B}x - {2*A}y\n\n"
653
 
654
  f"**Step 4:** Integrate du/dx with respect to x to find u(x, y).\n"
655
- f" u(x, y) = integral({-B}x - {2*A}y) dx\n"
656
- f" u(x, y) = -({B}/2)x^2 - {2*A}xy + g(y)\n"
657
  f"The 'constant' of integration, g(y), is an arbitrary function of y.\n\n"
658
-
659
  f"**Step 5:** Provide the simplest form for u(x, y).\n"
660
  f"To find the simplest expression, we set the integration constant g(y) to zero.\n"
661
  f" u(x, y) = {u_expr_sol}\n\n"
662
-
663
  f"**Answer:**\n"
664
  f"The simplest expression for the x-component of velocity is u = {u_expr_sol}."
665
  )
 
166
 
167
  # 2. Perform calculations and generate strings based on the chosen geometry
168
  if geometry == 'pipe':
169
+ # Pipe-specific parameters
170
  diameter_mm = random.randint(20, 200)
171
  radius_m = diameter_mm / 2000.0
172
 
173
+ # Core calculations for the pipe
174
  area = math.pi * radius_m**2
175
  # For a parabolic profile in a pipe, the exact integral yields Q = (1/2) * U_max * A
176
  flow_rate = 0.5 * u_max * area
177
  avg_velocity = u_max / 2.0
178
 
179
+ # Generate question and solution strings for the pipe
180
  question = (
181
  f"The velocity profile for a fluid flowing through a circular pipe with a diameter of {diameter_mm} mm "
182
  f"is given by u(r) = U_max * (1 - (r/R)^2), where R is the radius of the pipe and the maximum "
 
221
  )
222
 
223
  else: # geometry == 'channel'
224
+ # Channel-specific parameters
225
  height_cm = random.randint(5, 50)
226
  width_cm = random.randint(10, 100)
227
  height_m = height_cm / 100.0
228
  width_m = width_cm / 100.0
229
 
230
+ # Core calculations for the channel
231
  area = width_m * height_m
232
  # For a linear profile in a channel, the exact integral yields Q = (1/2) * U_max * A
233
  flow_rate = 0.5 * u_max * area
234
  avg_velocity = u_max / 2.0
235
 
236
+ # Generate question and solution strings for the channel
237
  question = (
238
  f"A fluid flows through a rectangular channel that is {width_cm} cm wide and {height_cm} cm high. "
239
  f"The velocity profile is given by u(y) = U_max * (y/H), where H is the channel height and the "
 
319
 
320
  # 2. Perform calculations and generate strings based on the flow type
321
  if flow_type == '2D':
322
+ # Define a more complex 2D velocity field
323
  # u = A*x*y, v = B*x^2 + C*y^2
324
  # This makes derivatives dependent on the point (x, y).
325
 
326
+ # Core calculations for 2D flow
327
  du_dy_val = A * x_point
328
  dv_dx_val = 2 * B * x_point
329
 
330
  zeta_z = dv_dx_val - du_dy_val
331
  is_irrotational = abs(zeta_z) < 1e-9 # Check for zero with floating point tolerance
332
 
333
+ # Generate question and solution strings for 2D flow
334
  question = (
335
  f"A 2D fluid flow is described by the velocity field:\n"
336
  f"u = {A}xy\n"
 
377
  F = random.randint(-4, 4)
378
  z_point = round(random.uniform(0.5, 3.0), 1)
379
 
380
+ # Define a more complex 3D velocity field
381
  # u = A*y^2, v = B*z^2 + C*x, w = D*x^2 + E*y
382
  # This allows all components of vorticity to be non-zero and position-dependent.
383
 
384
+ # Core calculations for 3D flow
385
  du_dy_val = 2 * A * y_point
386
  du_dz_val = 0
387
 
 
397
 
398
  is_irrotational = (abs(zeta_x) < 1e-9 and abs(zeta_y) < 1e-9 and abs(zeta_z) < 1e-9)
399
 
400
+ # Generate question and solution strings for 3D flow
401
  question = (
402
  f"A 3D fluid flow is described by the velocity field:\n"
403
  f"u = {A}y^2\n"
 
456
  Scenario:
457
  This template distinguishes between the Eulerian description (velocity field)
458
  and the Lagrangian description (individual particle path). It requires solving
459
+ a system of ordinary differential equations (ODEs).
 
 
460
 
461
  Core Equations:
462
  Pathline Definition: dx/dt = u(x, y) and dy/dt = v(x, y)
 
465
  Returns:
466
  tuple: A tuple containing:
467
  - str: A question asking for the particle's final coordinates.
468
+ - str: A step-by-step solution showing the derivation.
469
  """
470
+ # 1. Parameterize the inputs with physically realistic values
471
+ # We reduce the magnitude of A and B to ensure the exponential growth doesn't
472
+ # produce physically absurd velocities (e.g. keeping v < 100 m/s).
473
+ # Units for A and B are 1/s.
474
+ A = round(random.uniform(0.1, 0.5), 1)
475
+ B = round(random.uniform(0.1, 0.5), 1)
476
 
477
+ x0 = round(random.uniform(0.5, 2.0), 1)
478
+ y0 = round(random.uniform(0.5, 2.0), 1)
479
+ tf = round(random.uniform(1.0, 4.0), 1)
480
 
481
+ precision = 3
482
 
483
  # 2. Perform the core calculations for the solution
 
484
  # x(t) = x0 * exp(A*t)
485
  # y(t) = y0 * exp(-B*t)
486
  xf = x0 * math.exp(A * tf)
 
488
 
489
  # 3. Generate the question and solution strings
490
  question = (
491
+ f"A steady, 2D velocity field is defined by the equations u = {A}x and v = -{B}y, "
492
+ f"where u and v are in m/s, x and y are in meters, and the constants have units of s^-1.\n\n"
493
+ f"A fluid particle is located at the initial position (x0, y0) = ({x0} m, {y0} m) at time t = 0 s.\n"
494
  f"Determine the particle's coordinates (x, y) at time t = {tf} s."
495
  )
496
 
 
501
  f"Final time (tf) = {tf} s\n\n"
502
 
503
  f"**Step 1:** Set up the differential equation for the x-coordinate.\n"
504
+ f"The pathline is defined by the rate of change of the particle's position: dx/dt = u.\n"
505
+ f" dx/dt = {A}x\n"
506
+ f"\n\n"
507
 
508
  f"**Step 2:** Solve the ODE for x(t) by separating variables.\n"
509
  f" (1/x) dx = {A} dt\n"
 
532
 
533
  f"**Step 6:** Calculate the final position at t = {tf} s.\n"
534
  f"Substitute t = {tf} into the pathline equations:\n"
535
+ f" x({tf}) = {x0} * exp({A} * {tf}) = {round(xf, precision)} m\n"
536
+ f" y({tf}) = {y0} * exp(-{B} * {tf}) = {round(yf, precision)} m\n\n"
537
 
538
  f"**Answer:**\n"
539
+ f"At t = {tf} s, the particle's coordinates are ({round(xf, precision)} m, {round(yf, precision)} m)."
540
  )
541
 
542
  return question, solution
 
551
  This problem uses the differential form of the conservation of mass
552
  (continuity equation) for a 2D, incompressible flow. Given one velocity
553
  component, the user must find the other by performing partial differentiation
554
+ and integration. The problem asks for the simplest possible expression.
 
555
 
556
  Core Equation:
557
  2D Incompressible Continuity: du/dx + dv/dy = 0
 
561
  - str: A question asking for the unknown velocity component.
562
  - str: A step-by-step solution showing the derivation.
563
  """
564
+ # Helper to format algebraic terms cleanly (e.g. handles + -3x -> - 3x)
565
+ def fmt_term(coeff, var, is_first=False):
566
+ if coeff == 0:
567
+ return ""
568
+
569
+ # Determine sign prefix
570
+ if coeff < 0:
571
+ sign = "-" if is_first else "- "
572
+ val = -coeff
573
+ else:
574
+ sign = "" if is_first else "+ "
575
+ val = coeff
576
+
577
+ # Format number (remove .0 if integer)
578
+ if isinstance(val, float) and val.is_integer():
579
+ val = int(val)
580
+
581
+ # Construct string
582
+ # If coeff is 1 and there is a variable, omit the '1'
583
+ if var:
584
+ if val == 1:
585
+ return f"{sign}{var}"
586
+ else:
587
+ return f"{sign}{val}{var}"
588
+ else:
589
+ return f"{sign}{val}"
590
+
591
+ # Helper to join two terms into an expression
592
+ def build_poly(c1, v1, c2, v2):
593
+ t1 = fmt_term(c1, v1, is_first=True)
594
+ t2 = fmt_term(c2, v2, is_first=False)
595
+ return f"{t1} {t2}".strip()
596
+
597
  # 1. Parameterize the inputs with random values
 
 
598
  given_component = random.choice(['u', 'v'])
599
+
600
+ # Random non-zero coefficients
601
  A = random.choice([i for i in range(-5, 6) if i != 0])
602
  B = random.choice([i for i in range(-5, 6) if i != 0])
603
 
604
  # 2. Perform calculations and generate strings based on the chosen component
605
  if given_component == 'u':
606
+ # The u-component is given, find v
607
+ # u = Ax^2 + Bxy
608
+ u_expr = build_poly(A, "x^2", B, "xy")
609
+
610
+ # du/dx = 2Ax + By
611
+ du_dx_c1, du_dx_c2 = 2*A, B
612
+ du_dx_expr = build_poly(du_dx_c1, "x", du_dx_c2, "y")
613
 
614
+ # dv/dy = -du/dx = -2Ax - By
615
+ dv_dy_c1, dv_dy_c2 = -2*A, -B
616
+ dv_dy_expr = build_poly(dv_dy_c1, "x", dv_dy_c2, "y")
617
+
618
+ # v = integral(-2Ax - By) dy = -2Axy - (B/2)y^2
619
+ v_c1, v_c2 = -2*A, -B/2.0
620
+ v_expr = build_poly(v_c1, "xy", v_c2, "y^2")
621
 
622
  question = (
623
  f"A 2D, steady, incompressible flow has a velocity component in the x-direction "
 
629
  f"**Given:**\n"
630
  f"The flow is 2D, steady, and incompressible.\n"
631
  f"The u-component of velocity is u = {u_expr}\n\n"
632
+
633
  f"**Step 1:** State the 2D incompressible continuity equation.\n"
634
  f"The equation is: du/dx + dv/dy = 0\n\n"
635
+
636
+
637
  f"**Step 2:** Calculate the partial derivative of the given u-component with respect to x.\n"
638
  f" du/dx = d/dx({u_expr})\n"
639
  f" du/dx = {du_dx_expr}\n\n"
640
+
641
  f"**Step 3:** Rearrange the continuity equation to solve for dv/dy.\n"
642
  f" dv/dy = -du/dx\n"
643
+ f" dv/dy = -({du_dx_expr}) = {dv_dy_expr}\n\n"
644
 
645
  f"**Step 4:** Integrate dv/dy with respect to y to find v(x, y).\n"
646
+ f" v(x, y) = integral({dv_dy_expr}) dy\n"
647
+ f" v(x, y) = {v_expr} + f(x)\n"
648
  f"The 'constant' of integration, f(x), is an arbitrary function of x.\n\n"
649
+
650
  f"**Step 5:** Provide the simplest form for v(x, y).\n"
651
  f"To find the simplest expression, we set the integration constant f(x) to zero.\n"
652
  f" v(x, y) = {v_expr}\n\n"
653
+
654
  f"**Answer:**\n"
655
  f"The simplest expression for the y-component of velocity is v = {v_expr}."
656
  )
657
 
658
  else: # given_component == 'v'
659
+ # The v-component is given, find u
660
+ # v = Ay^2 + Bxy
661
+ v_expr = build_poly(A, "y^2", B, "xy")
662
 
663
+ # dv/dy = 2Ay + Bx
664
+ dv_dy_c1, dv_dy_c2 = 2*A, B
665
+ dv_dy_expr = build_poly(dv_dy_c1, "y", dv_dy_c2, "x")
666
+
667
+ # du/dx = -dv/dy = -2Ay - Bx
668
+ # Usually written as x term first: -Bx - 2Ay
669
+ du_dx_c1, du_dx_c2 = -B, -2*A
670
+ du_dx_expr = build_poly(du_dx_c1, "x", du_dx_c2, "y")
671
+
672
+ # u = integral(-Bx - 2Ay) dx = -(B/2)x^2 - 2Axy
673
+ u_c1, u_c2 = -B/2.0, -2*A
674
+ u_expr_sol = build_poly(u_c1, "x^2", u_c2, "xy")
675
 
676
  question = (
677
  f"A 2D, steady, incompressible flow has a velocity component in the y-direction "
 
683
  f"**Given:**\n"
684
  f"The flow is 2D, steady, and incompressible.\n"
685
  f"The v-component of velocity is v = {v_expr}\n\n"
686
+
687
  f"**Step 1:** State the 2D incompressible continuity equation.\n"
688
  f"The equation is: du/dx + dv/dy = 0\n\n"
689
+
690
  f"**Step 2:** Calculate the partial derivative of the given v-component with respect to y.\n"
691
  f" dv/dy = d/dy({v_expr})\n"
692
  f" dv/dy = {dv_dy_expr}\n\n"
693
+
694
  f"**Step 3:** Rearrange the continuity equation to solve for du/dx.\n"
695
  f" du/dx = -dv/dy\n"
696
+ f" du/dx = -({dv_dy_expr}) = {du_dx_expr}\n\n"
697
 
698
  f"**Step 4:** Integrate du/dx with respect to x to find u(x, y).\n"
699
+ f" u(x, y) = integral({du_dx_expr}) dx\n"
700
+ f" u(x, y) = {u_expr_sol} + g(y)\n"
701
  f"The 'constant' of integration, g(y), is an arbitrary function of y.\n\n"
702
+
703
  f"**Step 5:** Provide the simplest form for u(x, y).\n"
704
  f"To find the simplest expression, we set the integration constant g(y) to zero.\n"
705
  f" u(x, y) = {u_expr_sol}\n\n"
706
+
707
  f"**Answer:**\n"
708
  f"The simplest expression for the x-component of velocity is u = {u_expr_sol}."
709
  )
data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_statics.py CHANGED
@@ -11,11 +11,10 @@ def template_hydrostatic_pressure_at_depth():
11
  Scenario:
12
  This template generates a fundamental problem to calculate the absolute
13
  pressure at a specified depth within a fluid. It considers both the
14
- pressure exerted by the fluid column and the pressure at the free surface,
15
- applying the basic hydrostatic pressure equation.
16
 
17
  Core Equation:
18
- p_absolute = p_surface + (rho * g * h)
19
 
20
  Returns:
21
  tuple: A tuple containing:
@@ -27,36 +26,51 @@ def template_hydrostatic_pressure_at_depth():
27
  # Randomly select a fluid and its properties
28
  fluid_name, density_rho = random.choice(list(FLUID_DENSITIES.items()))
29
 
30
- # Randomize depth in meters
31
- depth_h = round(random.uniform(5.0, 500.0), 2)
32
 
33
  # Randomly decide if the surface pressure is atmospheric or a specified gauge pressure
34
  is_surface_atmospheric = random.choice([True, False])
 
 
 
35
 
36
  if is_surface_atmospheric:
37
- surface_pressure_kpa = ATMOSPHERIC_PRESSURE_KPA
38
- pressure_type_desc = "is atmospheric pressure"
 
 
 
39
  else:
40
- # Generate a random gauge pressure at the surface
41
- surface_pressure_kpa = round(random.uniform(10.0, 200.0), 2)
42
- pressure_type_desc = f"has a gauge pressure of {surface_pressure_kpa} kPa"
 
 
 
 
 
 
 
 
 
43
 
44
  # Standardize precision for final outputs
45
  precision = 3
46
 
47
  # 2. Perform the core calculations for the solution
48
 
49
- # Step A: Ensure all units are consistent (convert surface pressure to Pascals)
50
- surface_pressure_pa = surface_pressure_kpa * 1000
51
 
52
- # Step B: Calculate the pressure increase due to the fluid column (Gauge Pressure)
53
- # This is the result of p_gauge = rho * g * h
54
  pressure_increase_pa = density_rho * GRAVITY * depth_h
55
 
56
  # Step C: Calculate the final absolute pressure in Pascals
57
  absolute_pressure_pa = surface_pressure_pa + pressure_increase_pa
58
 
59
- # Step D: Convert the final answer back to kilopascals (kPa) for readability
60
  absolute_pressure_kpa = absolute_pressure_pa / 1000
61
 
62
  # 3. Generate the question and solution strings
@@ -64,39 +78,39 @@ def template_hydrostatic_pressure_at_depth():
64
  question = (
65
  f"An object is located {depth_h} m below the surface of a tank containing "
66
  f"{fluid_name.lower()}. The pressure at the free surface {pressure_type_desc}. "
67
- f"Assuming the density of {fluid_name.lower()} is {density_rho} kg/m^3, "
68
  f"what is the absolute pressure at this depth?"
69
  )
70
 
71
  solution = (
72
  f"**Given:**\n"
73
- f"Fluid: {fluid_name}\n"
74
- f"Density of Fluid (rho): {density_rho} kg/m^3\n"
75
  f"Depth (h): {depth_h} m\n"
76
- f"Surface Pressure (p_surface): {surface_pressure_kpa} kPa\n"
 
77
  f"Acceleration due to Gravity (g): {GRAVITY} m/s^2\n\n"
78
 
79
- f"**Step 1:** Ensure Consistent Units\n"
80
- f"The calculations require pressure to be in Pascals (Pa) to be consistent with other SI units.\n"
81
- f"p_surface = {surface_pressure_kpa} kPa * 1000 = {surface_pressure_pa:.2f} Pa\n\n"
82
 
83
- f"**Step 2:** Calculate the Pressure Increase Due to the Fluid Column\n"
84
- f"The pressure exerted by the fluid at a given depth is calculated using the formula: p_increase = rho * g * h.\n"
85
- f"p_increase = {density_rho} kg/m^3 * {GRAVITY} m/s^2 * {depth_h} m\n"
86
- f"p_increase = {pressure_increase_pa:.2f} Pa\n\n"
 
87
 
88
- f"**Step 3:** Calculate the Absolute Pressure at the Specified Depth\n"
89
- f"The absolute pressure is the sum of the surface pressure and the pressure increase due to the fluid column.\n"
90
- f"Formula: p_absolute = p_surface + p_increase\n"
91
- f"p_absolute = {surface_pressure_pa:.2f} Pa + {pressure_increase_pa:.2f} Pa\n"
92
- f"p_absolute = {absolute_pressure_pa:.2f} Pa\n\n"
93
 
94
- f"**Step 4:** Convert the Final Answer to Kilopascals (kPa)\n"
95
- f"For convenience, we convert the final pressure from Pascals back to kilopascals.\n"
96
- f"p_absolute = {absolute_pressure_pa:.2f} Pa / 1000 = {round(absolute_pressure_kpa, precision)} kPa\n\n"
97
 
98
  f"**Answer:**\n"
99
- f"The absolute pressure at a depth of {depth_h} m is {round(absolute_pressure_kpa, precision)} kPa."
100
  )
101
 
102
  return question, solution
@@ -203,40 +217,47 @@ def template_utube_manometer():
203
  - str: A question asking for the gauge pressure in a pipe.
204
  - str: A step-by-step solution to the problem.
205
  """
206
- # 1. Parameterize the inputs with random values
207
-
208
- # Randomly select a fluid for the pipe
209
- pipe_fluid_name, rho1 = random.choice(list(PIPE_FLUIDS.items()))
210
-
211
- # Randomly select a fluid for the manometer
212
- manometer_fluid_name, rho2 = random.choice(list(MANOMETER_FLUIDS.items()))
213
-
214
- # --- Ensure physical realism: manometer fluid must be denser ---
215
- # If the chosen pipe fluid is denser than the manometer fluid, re-select
216
- # the manometer fluid until it is denser. This avoids nonsensical scenarios.
217
- while rho2 <= rho1:
218
  manometer_fluid_name, rho2 = random.choice(list(MANOMETER_FLUIDS.items()))
219
 
220
- # Randomize the vertical heights, in meters
221
- # h1: distance from pipe centerline down to the fluid interface
222
- h1 = round(random.uniform(0.1, 0.5), 2)
223
- # h2: the height difference between the two manometer fluid columns
224
- h2 = round(random.uniform(0.05, 0.75), 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
  # Standardize precision for final outputs
227
  precision = 3
228
 
229
- # 2. Perform the core calculations for the solution
230
-
231
- # Step A: Calculate the pressure contribution from the pipe fluid column (rho1*g*h1)
232
- pressure_term1_pa = rho1 * GRAVITY * h1
233
-
234
- # Step B: Calculate the pressure contribution from the manometer fluid column (rho2*g*h2)
235
- pressure_term2_pa = rho2 * GRAVITY * h2
236
-
237
- # Step C: Calculate the gauge pressure in Pascals (Pa)
238
- gauge_pressure_pa = pressure_term2_pa - pressure_term1_pa
239
-
240
  # Step D: Convert the final answer to kilopascals (kPa) for readability
241
  gauge_pressure_kpa = gauge_pressure_pa / 1000
242
 
@@ -319,7 +340,7 @@ def template_floating_object_submersion_depth():
319
  obj_material, rho_object = random.choice(list(MATERIAL_DENSITIES.items()))
320
  fluid_name, rho_fluid = random.choice(list(FLUID_DENSITIES.items()))
321
 
322
- # CRITICAL: Ensure the object will actually float ---
323
  # Re-select until the object's density is less than the fluid's density.
324
  max_attempts = 20
325
  attempt = 0
 
11
  Scenario:
12
  This template generates a fundamental problem to calculate the absolute
13
  pressure at a specified depth within a fluid. It considers both the
14
+ pressure exerted by the fluid column and the pressure at the free surface.
 
15
 
16
  Core Equation:
17
+ p_absolute = p_surface_absolute + (rho * g * h)
18
 
19
  Returns:
20
  tuple: A tuple containing:
 
26
  # Randomly select a fluid and its properties
27
  fluid_name, density_rho = random.choice(list(FLUID_DENSITIES.items()))
28
 
29
+ # Randomize depth in meters (Restricted to realistic tank depths)
30
+ depth_h = round(random.uniform(2.0, 25.0), 2)
31
 
32
  # Randomly decide if the surface pressure is atmospheric or a specified gauge pressure
33
  is_surface_atmospheric = random.choice([True, False])
34
+
35
+ # Define Atmospheric Pressure
36
+ P_atm_kpa = 101.325
37
 
38
  if is_surface_atmospheric:
39
+ # Surface is open to atmosphere
40
+ surface_pressure_input_kpa = P_atm_kpa
41
+ surface_pressure_abs_kpa = P_atm_kpa
42
+ pressure_type_desc = "is exposed to the atmosphere"
43
+ surface_calc_note = "Since the surface is open to the atmosphere, the surface absolute pressure is just P_atm."
44
  else:
45
+ # Surface is pressurized (Gauge Pressure given)
46
+ surface_gauge_kpa = round(random.uniform(10.0, 200.0), 2)
47
+ surface_pressure_input_kpa = surface_gauge_kpa
48
+
49
+ # FIX: Absolute Surface Pressure = Gauge + Atm
50
+ surface_pressure_abs_kpa = surface_gauge_kpa + P_atm_kpa
51
+
52
+ pressure_type_desc = f"is maintained at a gauge pressure of {surface_gauge_kpa} kPa"
53
+ surface_calc_note = (
54
+ f"The problem gives gauge pressure. To get absolute pressure at the surface, we add atmospheric pressure:\n"
55
+ f"P_surface_abs = P_gauge + P_atm = {surface_gauge_kpa} + {P_atm_kpa} = {surface_pressure_abs_kpa:.3f} kPa"
56
+ )
57
 
58
  # Standardize precision for final outputs
59
  precision = 3
60
 
61
  # 2. Perform the core calculations for the solution
62
 
63
+ # Step A: Convert absolute surface pressure to Pascals
64
+ surface_pressure_pa = surface_pressure_abs_kpa * 1000
65
 
66
+ # Step B: Calculate the pressure increase due to the fluid column (Hydrostatic Pressure)
67
+ # p_hydro = rho * g * h
68
  pressure_increase_pa = density_rho * GRAVITY * depth_h
69
 
70
  # Step C: Calculate the final absolute pressure in Pascals
71
  absolute_pressure_pa = surface_pressure_pa + pressure_increase_pa
72
 
73
+ # Step D: Convert the final answer back to kilopascals (kPa)
74
  absolute_pressure_kpa = absolute_pressure_pa / 1000
75
 
76
  # 3. Generate the question and solution strings
 
78
  question = (
79
  f"An object is located {depth_h} m below the surface of a tank containing "
80
  f"{fluid_name.lower()}. The pressure at the free surface {pressure_type_desc}. "
81
+ f"Assuming the density of {fluid_name.lower()} is {density_rho} kg/m^3 and standard atmospheric pressure is {P_atm_kpa} kPa, "
82
  f"what is the absolute pressure at this depth?"
83
  )
84
 
85
  solution = (
86
  f"**Given:**\n"
87
+ f"Fluid: {fluid_name} (Density rho = {density_rho} kg/m^3)\n"
 
88
  f"Depth (h): {depth_h} m\n"
89
+ f"Surface Condition: {pressure_type_desc}\n"
90
+ f"Standard Atmospheric Pressure (P_atm): {P_atm_kpa} kPa\n"
91
  f"Acceleration due to Gravity (g): {GRAVITY} m/s^2\n\n"
92
 
93
+ f"**Step 1:** Determine the Absolute Pressure at the Surface\n"
94
+ f"{surface_calc_note}\n"
95
+ f"Convert to Pascals: P_surface_abs = {surface_pressure_abs_kpa:.3f} kPa * 1000 = {surface_pressure_pa:.1f} Pa\n\n"
96
 
97
+ f"**Step 2:** Calculate the Hydrostatic Pressure Increase\n"
98
+ f"The pressure exerted by the fluid column is calculated using: P_hydro = rho * g * h.\n"
99
+ f"P_hydro = {density_rho} kg/m^3 * {GRAVITY} m/s^2 * {depth_h} m\n"
100
+ f"P_hydro = {pressure_increase_pa:.1f} Pa\n"
101
+ f"\n\n"
102
 
103
+ f"**Step 3:** Calculate Total Absolute Pressure at Depth\n"
104
+ f"The absolute pressure at depth is the sum of the absolute surface pressure and the hydrostatic pressure.\n"
105
+ f"P_absolute = P_surface_abs + P_hydro\n"
106
+ f"P_absolute = {surface_pressure_pa:.1f} Pa + {pressure_increase_pa:.1f} Pa\n"
107
+ f"P_absolute = {absolute_pressure_pa:.1f} Pa\n\n"
108
 
109
+ f"**Step 4:** Convert Final Answer to kPa\n"
110
+ f"P_absolute = {absolute_pressure_pa:.1f} Pa / 1000 = {round(absolute_pressure_kpa, precision)} kPa\n\n"
 
111
 
112
  f"**Answer:**\n"
113
+ f"The absolute pressure at a depth of {depth_h} m is **{round(absolute_pressure_kpa, precision)} kPa**."
114
  )
115
 
116
  return question, solution
 
217
  - str: A question asking for the gauge pressure in a pipe.
218
  - str: A step-by-step solution to the problem.
219
  """
220
+ # 1. Parameterize the inputs with a loop to ensure positive gauge pressure
221
+ # This prevents physically confusing scenarios where the description says the open arm
222
+ # level is "higher" but the math yields suction (negative pressure).
223
+ while True:
224
+ # Randomly select a fluid for the pipe
225
+ pipe_fluid_name, rho1 = random.choice(list(PIPE_FLUIDS.items()))
226
+
227
+ # Randomly select a fluid for the manometer
 
 
 
 
228
  manometer_fluid_name, rho2 = random.choice(list(MANOMETER_FLUIDS.items()))
229
 
230
+ # Ensure physical realism: manometer fluid must be denser
231
+ # If the chosen pipe fluid is denser than the manometer fluid, re-select
232
+ # the manometer fluid until it is denser.
233
+ if rho2 <= rho1:
234
+ continue
235
+
236
+ # Randomize the vertical heights, in meters
237
+ # h1: distance from pipe centerline down to the fluid interface
238
+ h1 = round(random.uniform(0.1, 0.5), 2)
239
+ # h2: the height difference between the two manometer fluid columns
240
+ h2 = round(random.uniform(0.05, 0.75), 2)
241
+
242
+ # 2. Perform the core calculations for verification
243
+
244
+ # Step A: Calculate the pressure contribution from the pipe fluid column (rho1*g*h1)
245
+ pressure_term1_pa = rho1 * GRAVITY * h1
246
+
247
+ # Step B: Calculate the pressure contribution from the manometer fluid column (rho2*g*h2)
248
+ pressure_term2_pa = rho2 * GRAVITY * h2
249
+
250
+ # Step C: Calculate the gauge pressure in Pascals (Pa)
251
+ gauge_pressure_pa = pressure_term2_pa - pressure_term1_pa
252
+
253
+ # Condition: Pressure must be positive to match the problem description
254
+ # (open arm level is "higher" implies P_pipe > P_atm)
255
+ if gauge_pressure_pa > 0:
256
+ break
257
 
258
  # Standardize precision for final outputs
259
  precision = 3
260
 
 
 
 
 
 
 
 
 
 
 
 
261
  # Step D: Convert the final answer to kilopascals (kPa) for readability
262
  gauge_pressure_kpa = gauge_pressure_pa / 1000
263
 
 
340
  obj_material, rho_object = random.choice(list(MATERIAL_DENSITIES.items()))
341
  fluid_name, rho_fluid = random.choice(list(FLUID_DENSITIES.items()))
342
 
343
+ # CRITICAL: Ensure the object will actually float
344
  # Re-select until the object's density is less than the fluid's density.
345
  max_attempts = 20
346
  attempt = 0
data/templates/branches/mechanical_engineering/mechanics_of_materials/__pycache__/stress_and_strain.cpython-311.pyc ADDED
Binary file (45.9 kB). View file
 
data/templates/branches/mechanical_engineering/mechanics_of_materials/__pycache__/torsion.cpython-311.pyc ADDED
Binary file (26.8 kB). View file
 
data/templates/branches/mechanical_engineering/mechanics_of_materials/stress_and_strain.py CHANGED
@@ -28,7 +28,7 @@ def template_basic_stress_strain():
28
  precision = 3 # Standardize precision for numerical stability and formatting
29
 
30
  if use_si_units:
31
- # --- SI Unit System ---
32
  load = random.randint(10, 500) # in kN
33
  length = round(random.uniform(0.5, 4.0), 2) # in meters
34
  elongation = round(random.uniform(0.5, 5.0), 2) # in mm
@@ -59,7 +59,7 @@ def template_basic_stress_strain():
59
  strain_unit_explanation = "mm/m or unitless"
60
 
61
  else:
62
- # --- US Customary Unit System ---
63
  load = random.randint(5, 100) # in kips
64
  length = round(random.uniform(24.0, 120.0), 1) # in inches
65
  elongation = round(random.uniform(0.05, 0.25), 3) # in inches
@@ -197,7 +197,7 @@ def template_axial_deformation():
197
  precision = 3
198
 
199
  if use_si_units:
200
- # --- SI Unit System ---
201
  material_E = MATERIAL_PROPERTIES[material_name]['E_GPa'] # in GPa
202
  length = round(random.uniform(0.5, 5.0), 2) # in m
203
 
@@ -234,7 +234,7 @@ def template_axial_deformation():
234
  elongation_str = f"{round(elongation, precision)} mm"
235
 
236
  else:
237
- # --- US Customary Unit System ---
238
  material_E = MATERIAL_PROPERTIES[material_name]['E_ksi'] # in ksi
239
  length = round(random.uniform(12.0, 150.0), 1) # in inches
240
 
@@ -392,71 +392,78 @@ def template_multi_segment_rod():
392
  - str: A question about the total deformation of a composite rod.
393
  - str: A step-by-step solution.
394
  """
395
- # 1. Parameterize inputs
396
- use_si_units = random.choice([True, False])
397
- num_segments = random.choice([2, 3])
398
  precision = 4
399
 
400
- segments = []
401
- loads = {} # Loads applied at nodes {node_index: load_value}
402
 
403
- # Generate properties for each segment
404
- for i in range(num_segments):
405
- material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
406
- segment = {'material_name': material_name}
407
 
408
- if use_si_units:
409
- segment['length'] = round(random.uniform(0.2, 1.5), 2) # meters
410
- # For simplicity, all segments are circular
411
- diameter = round(random.uniform(20, 75), 1) # mm
412
- segment['area'] = math.pi * (diameter / 2)**2
413
- segment['E'] = MATERIAL_PROPERTIES[material_name]['E_GPa']
414
- segment['dim_str'] = f"{diameter} mm diameter"
415
- # Node i+1 is the right end of segment i
416
- loads[i+1] = random.randint(-250, 250) # kN
417
- else:
418
- segment['length'] = round(random.uniform(10.0, 60.0), 1) # inches
419
- diameter = round(random.uniform(0.75, 3.0), 1) # inches
420
- segment['area'] = math.pi * (diameter / 2)**2
421
- segment['E'] = MATERIAL_PROPERTIES[material_name]['E_ksi']
422
- segment['dim_str'] = f"{diameter} in diameter"
423
- loads[i+1] = random.randint(-60, 60) # kips
424
-
425
- segments.append(segment)
426
-
427
- # 2. Perform Core Calculations
428
- # A. Calculate internal forces (P_i) in each segment
429
- # We work from the free end (right) to the fixed end (left)
430
- total_deformation = 0
431
- cumulative_load = 0
432
- # Iterate backwards from the last segment to the first
433
- for i in range(num_segments - 1, -1, -1):
434
- # The internal force in segment 'i' is the sum of all external loads to its right
435
- node_index = i + 1
436
- cumulative_load += loads[node_index]
437
- segments[i]['internal_load'] = cumulative_load
438
-
439
- # B. Calculate deformation (delta_i) for each segment and sum them
440
- for i in range(num_segments):
441
- P = segments[i]['internal_load']
442
- L = segments[i]['length']
443
- A = segments[i]['area']
444
- E = segments[i]['E']
445
 
446
- if use_si_units:
447
- # Convert units for consistency: N, mm, MPa (N/mm^2)
448
- # P: kN → N (multiply by 1000)
449
- # L: m → mm (multiply by 1000)
450
- # A: already in mm²
451
- # E: GPa → MPa (multiply by 1000)
452
- # delta = (P_N * L_mm) / (A_mm2 * E_MPa)
453
- delta = (P * 1000 * L * 1000) / (A * E * 1000) # mm
454
- else:
455
- # Units are already consistent: kips, inches, ksi (kip/in^2)
456
- delta = (P * L) / (A * E) # inches
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
 
458
- segments[i]['deformation'] = delta
459
- total_deformation += delta
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
  # 3. Generate Question and Solution Strings
462
  question = (
@@ -527,15 +534,28 @@ def template_multi_segment_rod():
527
 
528
  for i, seg in enumerate(segments):
529
  solution += f" - **Segment {i+1} ({seg['material_name']}):**\n"
530
- solution += (
531
- f"P{i+1} = {seg['internal_load']} {unit_P}\n"
532
- f"L{i+1} = {seg['length']} {unit_L}\n"
533
- f"A{i+1} = {round(seg['area'], precision)} {'mm²' if use_si_units else 'in²'}\n"
534
- f"E{i+1} = {seg['E']} {unit_E}\n"
535
- f"δ{i+1} = ({seg['internal_load']} × {seg['length']}) / ({round(seg['area'], precision)} × {seg['E']}) = {round(seg['deformation'], precision)} {unit_D}\n"
536
- )
537
  if use_si_units:
538
- solution += f"*Note:* Calculation uses consistent units (N, mm, MPa).\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
 
540
  solution += (
541
  f"\n**Step 3:** Calculate the Total Deformation\n"
@@ -580,54 +600,76 @@ def template_poissons_ratio():
580
  use_si_units = random.choice([True, False])
581
  material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
582
  material = MATERIAL_PROPERTIES[material_name]
583
- precision = 5 # Use higher precision for small strain values
584
 
585
- # For this problem, the cross-section is always circular
586
  if use_si_units:
587
- # SI Unit System
588
- load_kN = random.randint(50, 950) * random.choice([-1, 1]) # in kN (tensile or compressive)
589
- initial_length_m = round(random.uniform(0.5, 3.0), 2) # in meters
590
- initial_diameter_mm = random.randint(25, 125) # in mm
591
-
592
  area_mm2 = math.pi * (initial_diameter_mm / 2)**2
593
  E_GPa = material['E_GPa']
594
  nu = material['nu']
595
-
596
- # Core Calculations (Units: N, mm, MPa)
 
 
 
 
 
 
 
 
 
 
 
 
 
597
  stress_MPa = (load_kN * 1000) / area_mm2
598
  axial_strain = stress_MPa / (E_GPa * 1000)
599
- lateral_strain = -nu * axial_strain
600
- delta_diameter_mm = initial_diameter_mm * lateral_strain
601
- final_diameter_mm = initial_diameter_mm + delta_diameter_mm
602
-
603
- # Formatting for question and solution
604
- load_str = f"{load_kN} kN"
605
  load_type_str = "tension" if load_kN > 0 else "compression"
606
  dim_str = f"{initial_diameter_mm} mm"
607
  unit_d = "mm"
608
 
 
 
 
 
 
609
  else:
610
  # US Customary Unit System
611
- load_kips = random.randint(15, 250) * random.choice([-1, 1]) # in kips
612
- initial_length_in = round(random.uniform(20.0, 100.0), 1) # in inches
613
- initial_diameter_in = round(random.uniform(1.0, 5.0), 2) # in inches
614
-
615
  area_in2 = math.pi * (initial_diameter_in / 2)**2
616
  E_ksi = material['E_ksi']
617
  nu = material['nu']
618
 
619
- # Core Calculations (Units: kips, in, ksi)
 
 
 
 
 
 
 
 
 
 
620
  stress_ksi = load_kips / area_in2
621
  axial_strain = stress_ksi / E_ksi
622
- lateral_strain = -nu * axial_strain
623
- delta_diameter_in = initial_diameter_in * lateral_strain
624
- final_diameter_in = initial_diameter_in + delta_diameter_in
625
 
626
- # Formatting for question and solution
627
- load_str = f"{load_kips} kips"
628
  load_type_str = "tension" if load_kips > 0 else "compression"
629
  dim_str = f"{initial_diameter_in} in"
630
  unit_d = "in"
 
 
 
 
 
631
 
632
  # 2. Generate Question and Solution Strings
633
  question = (
@@ -645,16 +687,17 @@ def template_poissons_ratio():
645
  f"**Given:**\n"
646
  f"Material: {material_name} (E = {material['E_GPa' if use_si_units else 'E_ksi']} {'GPa' if use_si_units else 'ksi'}, nu = {nu})\n"
647
  f"Initial Diameter (d_0): {dim_str}\n"
648
- f"Axial Load (P): {load_str}\n\n"
649
 
650
  f"**Step 1:** Calculate Axial Strain (epsilon_axial)\n"
651
  f"First, we need the cross-sectional area (A) and the axial stress (sigma).\n"
652
  f"A = pi * (d_0 / 2)^2 = pi * ({initial_diameter_mm if use_si_units else initial_diameter_in} / 2)^2 = {round(area_mm2 if use_si_units else area_in2, 4)} {'mm^2' if use_si_units else 'in^2'}\n"
 
653
  )
654
 
655
  if use_si_units:
656
  solution += (
657
- f"sigma = P / A = ({load_kN} * 1000 N) / ({round(area_mm2, 4)} mm^2) = {round(stress_MPa, 3)} MPa\n"
658
  f"Now, calculate axial strain using Hooke's Law: epsilon_axial = sigma / E.\n"
659
  f"E = {E_GPa} GPa = {E_GPa * 1000} MPa\n"
660
  f"epsilon_axial = {round(stress_MPa, 3)} MPa / {E_GPa * 1000} MPa = {axial_strain:.4e}\n\n"
@@ -720,7 +763,7 @@ def template_statically_indeterminate():
720
  precision = 3
721
 
722
  if use_si_units:
723
- # --- SI Unit System ---
724
  total_length = round(random.uniform(1.0, 3.0), 2) # m
725
  len_AB = round(random.uniform(0.2 * total_length, 0.8 * total_length), 2) # m
726
  len_BC = total_length - len_AB
@@ -739,7 +782,7 @@ def template_statically_indeterminate():
739
  unit_L, unit_P, unit_S, unit_A, unit_E = "m", "kN", "MPa", "mm^2", "GPa"
740
 
741
  else:
742
- # --- US Customary Unit System ---
743
  total_length = round(random.uniform(40.0, 120.0), 1) # inches
744
  len_AB = round(random.uniform(0.2 * total_length, 0.8 * total_length), 1) # inches
745
  len_BC = total_length - len_AB
@@ -802,7 +845,7 @@ def template_statically_indeterminate():
802
  f"**Step 1:** Equation of Equilibrium\n"
803
  f"Summing forces in the x-direction (assuming right is positive):\n"
804
  f"Sum(F_x) = 0 => -R_A + P - R_C = 0\n"
805
- f"R_A + R_C = {load_P} {unit_P} ----(Equation 1)\n\n"
806
 
807
  f"**Step 2:** Equation of Compatibility\n"
808
  f"Since the rod is fixed between unyielding supports, the total deformation must be zero.\n"
@@ -812,7 +855,7 @@ def template_statically_indeterminate():
812
  f"Using the deformation formula delta = PL/AE:\n"
813
  f"(R_A * L_AB) / (A * E) - (R_C * L_BC) / (A * E) = 0\n"
814
  f"Since A and E are constant, they cancel out:\n"
815
- f"R_A * L_AB = R_C * L_BC ----(Equation 2)\n\n"
816
 
817
  f"**Step 3:** Solve for the Reaction Forces\n"
818
  f"From Equation 1, we can express R_C as: R_C = {load_P} - R_A.\n"
 
28
  precision = 3 # Standardize precision for numerical stability and formatting
29
 
30
  if use_si_units:
31
+ # SI Unit System
32
  load = random.randint(10, 500) # in kN
33
  length = round(random.uniform(0.5, 4.0), 2) # in meters
34
  elongation = round(random.uniform(0.5, 5.0), 2) # in mm
 
59
  strain_unit_explanation = "mm/m or unitless"
60
 
61
  else:
62
+ # US Customary Unit System
63
  load = random.randint(5, 100) # in kips
64
  length = round(random.uniform(24.0, 120.0), 1) # in inches
65
  elongation = round(random.uniform(0.05, 0.25), 3) # in inches
 
197
  precision = 3
198
 
199
  if use_si_units:
200
+ # SI Unit System
201
  material_E = MATERIAL_PROPERTIES[material_name]['E_GPa'] # in GPa
202
  length = round(random.uniform(0.5, 5.0), 2) # in m
203
 
 
234
  elongation_str = f"{round(elongation, precision)} mm"
235
 
236
  else:
237
+ # US Customary Unit System
238
  material_E = MATERIAL_PROPERTIES[material_name]['E_ksi'] # in ksi
239
  length = round(random.uniform(12.0, 150.0), 1) # in inches
240
 
 
392
  - str: A question about the total deformation of a composite rod.
393
  - str: A step-by-step solution.
394
  """
395
+ # 1. Parameterize inputs with validation loop
 
 
396
  precision = 4
397
 
398
+ # Define a safe list of structural materials (exclude rubber for this high-load problem)
399
+ structural_materials = [k for k in MATERIAL_PROPERTIES.keys() if 'Rubber' not in k]
400
 
401
+ while True:
402
+ use_si_units = random.choice([True, False])
403
+ num_segments = random.choice([2, 3])
 
404
 
405
+ segments = []
406
+ loads = {} # Loads applied at nodes {node_index: load_value}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
+ # Generate properties for each segment
409
+ for i in range(num_segments):
410
+ material_name = random.choice(structural_materials)
411
+ segment = {'material_name': material_name}
412
+
413
+ if use_si_units:
414
+ segment['length'] = round(random.uniform(0.2, 1.5), 2) # meters
415
+ diameter = round(random.uniform(20, 75), 1) # mm
416
+ segment['area'] = math.pi * (diameter / 2)**2
417
+ segment['E'] = MATERIAL_PROPERTIES[material_name]['E_GPa']
418
+ segment['dim_str'] = f"{diameter} mm diameter"
419
+ loads[i+1] = random.randint(-250, 250) # kN
420
+ else:
421
+ segment['length'] = round(random.uniform(10.0, 60.0), 1) # inches
422
+ diameter = round(random.uniform(0.75, 3.0), 1) # inches
423
+ segment['area'] = math.pi * (diameter / 2)**2
424
+ segment['E'] = MATERIAL_PROPERTIES[material_name]['E_ksi']
425
+ segment['dim_str'] = f"{diameter} in diameter"
426
+ loads[i+1] = random.randint(-60, 60) # kips
427
+
428
+ segments.append(segment)
429
+
430
+ # 2. Perform Core Calculations
431
+ total_deformation = 0
432
+ cumulative_load = 0
433
+ max_strain = 0
434
 
435
+ # Calculate internal forces and deformations
436
+ # Iterate backwards from the last segment to the first
437
+ for i in range(num_segments - 1, -1, -1):
438
+ node_index = i + 1
439
+ cumulative_load += loads[node_index]
440
+ segments[i]['internal_load'] = cumulative_load
441
+
442
+ for i in range(num_segments):
443
+ P = segments[i]['internal_load']
444
+ L = segments[i]['length']
445
+ A = segments[i]['area']
446
+ E = segments[i]['E']
447
+
448
+ if use_si_units:
449
+ # delta = (P_N * L_mm) / (A_mm2 * E_MPa)
450
+ # P in kN -> *1000 -> N
451
+ # L in m -> *1000 -> mm
452
+ # E in GPa -> *1000 -> MPa
453
+ delta = (P * 1000 * L * 1000) / (A * E * 1000) # mm
454
+ current_strain = abs(delta / (L * 1000))
455
+ else:
456
+ delta = (P * L) / (A * E) # inches
457
+ current_strain = abs(delta / L)
458
+
459
+ segments[i]['deformation'] = delta
460
+ total_deformation += delta
461
+ if current_strain > max_strain:
462
+ max_strain = current_strain
463
+
464
+ # Validate: Ensure max strain is < 1% (0.01) for linear elasticity to hold
465
+ if max_strain < 0.01:
466
+ break # Valid problem generated
467
 
468
  # 3. Generate Question and Solution Strings
469
  question = (
 
534
 
535
  for i, seg in enumerate(segments):
536
  solution += f" - **Segment {i+1} ({seg['material_name']}):**\n"
537
+
 
 
 
 
 
 
538
  if use_si_units:
539
+ # Explicitly show unit conversions in the solution string
540
+ P_disp = f"{seg['internal_load']} kN ({seg['internal_load']*1000:.0f} N)"
541
+ L_disp = f"{seg['length']} m ({seg['length']*1000:.0f} mm)"
542
+ E_disp = f"{seg['E']} GPa ({seg['E']*1000:.0f} MPa)"
543
+
544
+ solution += (
545
+ f"P{i+1} = {P_disp}\n"
546
+ f"L{i+1} = {L_disp}\n"
547
+ f"A{i+1} = {round(seg['area'], precision)} mm²\n"
548
+ f"E{i+1} = {E_disp}\n"
549
+ f"δ{i+1} = ({seg['internal_load']*1000:.0f} × {seg['length']*1000:.0f}) / ({round(seg['area'], precision)} × {seg['E']*1000:.0f}) = {round(seg['deformation'], precision)} mm\n"
550
+ )
551
+ else:
552
+ solution += (
553
+ f"P{i+1} = {seg['internal_load']} kips\n"
554
+ f"L{i+1} = {seg['length']} in\n"
555
+ f"A{i+1} = {round(seg['area'], precision)} in²\n"
556
+ f"E{i+1} = {seg['E']} ksi\n"
557
+ f"δ{i+1} = ({seg['internal_load']} × {seg['length']}) / ({round(seg['area'], precision)} × {seg['E']}) = {round(seg['deformation'], precision)} in\n"
558
+ )
559
 
560
  solution += (
561
  f"\n**Step 3:** Calculate the Total Deformation\n"
 
600
  use_si_units = random.choice([True, False])
601
  material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
602
  material = MATERIAL_PROPERTIES[material_name]
603
+ precision = 5
604
 
605
+ # Generate dimensions first
606
  if use_si_units:
607
+ initial_length_m = round(random.uniform(0.5, 3.0), 2)
608
+ initial_diameter_mm = random.randint(25, 125)
 
 
 
609
  area_mm2 = math.pi * (initial_diameter_mm / 2)**2
610
  E_GPa = material['E_GPa']
611
  nu = material['nu']
612
+
613
+ # Ensure Elastic Behavior
614
+ # Instead of random load, pick a safe target strain (0.1% to 0.4%)
615
+ target_strain = random.uniform(0.001, 0.004) * random.choice([-1, 1])
616
+
617
+ # Calculate Load required to achieve this strain: P = E * A * epsilon
618
+ # E in MPa (GPa * 1000), A in mm2 -> P in Newtons
619
+ required_load_N = (E_GPa * 1000) * area_mm2 * target_strain
620
+
621
+ # Convert to kN and round to look like a "given" problem value
622
+ load_kN = round(required_load_N / 1000.0)
623
+ # Ensure load is not zero
624
+ if load_kN == 0: load_kN = 1 if target_strain > 0 else -1
625
+
626
+ # Now recalculate exact stress/strain from this rounded load
627
  stress_MPa = (load_kN * 1000) / area_mm2
628
  axial_strain = stress_MPa / (E_GPa * 1000)
629
+
630
+ # Formatting
631
+ load_str = f"{abs(load_kN)} kN"
 
 
 
632
  load_type_str = "tension" if load_kN > 0 else "compression"
633
  dim_str = f"{initial_diameter_mm} mm"
634
  unit_d = "mm"
635
 
636
+ # Secondary calculations
637
+ lateral_strain = -nu * axial_strain
638
+ delta_diameter_mm = initial_diameter_mm * lateral_strain
639
+ final_diameter_mm = initial_diameter_mm + delta_diameter_mm
640
+
641
  else:
642
  # US Customary Unit System
643
+ initial_length_in = round(random.uniform(20.0, 100.0), 1)
644
+ initial_diameter_in = round(random.uniform(1.0, 5.0), 2)
 
 
645
  area_in2 = math.pi * (initial_diameter_in / 2)**2
646
  E_ksi = material['E_ksi']
647
  nu = material['nu']
648
 
649
+ # Ensure Elastic Behavior
650
+ target_strain = random.uniform(0.001, 0.004) * random.choice([-1, 1])
651
+
652
+ # Calculate Load: P = E * A * epsilon
653
+ required_load_kips = E_ksi * area_in2 * target_strain
654
+
655
+ # Round to integer kips
656
+ load_kips = round(required_load_kips)
657
+ if load_kips == 0: load_kips = 1 if target_strain > 0 else -1
658
+
659
+ # Recalculate exact values
660
  stress_ksi = load_kips / area_in2
661
  axial_strain = stress_ksi / E_ksi
 
 
 
662
 
663
+ # Formatting
664
+ load_str = f"{abs(load_kips)} kips"
665
  load_type_str = "tension" if load_kips > 0 else "compression"
666
  dim_str = f"{initial_diameter_in} in"
667
  unit_d = "in"
668
+
669
+ # Secondary calculations
670
+ lateral_strain = -nu * axial_strain
671
+ delta_diameter_in = initial_diameter_in * lateral_strain
672
+ final_diameter_in = initial_diameter_in + delta_diameter_in
673
 
674
  # 2. Generate Question and Solution Strings
675
  question = (
 
687
  f"**Given:**\n"
688
  f"Material: {material_name} (E = {material['E_GPa' if use_si_units else 'E_ksi']} {'GPa' if use_si_units else 'ksi'}, nu = {nu})\n"
689
  f"Initial Diameter (d_0): {dim_str}\n"
690
+ f"Axial Load (P): {load_str} ({load_type_str})\n\n"
691
 
692
  f"**Step 1:** Calculate Axial Strain (epsilon_axial)\n"
693
  f"First, we need the cross-sectional area (A) and the axial stress (sigma).\n"
694
  f"A = pi * (d_0 / 2)^2 = pi * ({initial_diameter_mm if use_si_units else initial_diameter_in} / 2)^2 = {round(area_mm2 if use_si_units else area_in2, 4)} {'mm^2' if use_si_units else 'in^2'}\n"
695
+ f"\n"
696
  )
697
 
698
  if use_si_units:
699
  solution += (
700
+ f"sigma = P / A = ({load_kN * 1000} N) / ({round(area_mm2, 4)} mm^2) = {round(stress_MPa, 3)} MPa\n"
701
  f"Now, calculate axial strain using Hooke's Law: epsilon_axial = sigma / E.\n"
702
  f"E = {E_GPa} GPa = {E_GPa * 1000} MPa\n"
703
  f"epsilon_axial = {round(stress_MPa, 3)} MPa / {E_GPa * 1000} MPa = {axial_strain:.4e}\n\n"
 
763
  precision = 3
764
 
765
  if use_si_units:
766
+ # SI Unit System
767
  total_length = round(random.uniform(1.0, 3.0), 2) # m
768
  len_AB = round(random.uniform(0.2 * total_length, 0.8 * total_length), 2) # m
769
  len_BC = total_length - len_AB
 
782
  unit_L, unit_P, unit_S, unit_A, unit_E = "m", "kN", "MPa", "mm^2", "GPa"
783
 
784
  else:
785
+ # US Customary Unit System
786
  total_length = round(random.uniform(40.0, 120.0), 1) # inches
787
  len_AB = round(random.uniform(0.2 * total_length, 0.8 * total_length), 1) # inches
788
  len_BC = total_length - len_AB
 
845
  f"**Step 1:** Equation of Equilibrium\n"
846
  f"Summing forces in the x-direction (assuming right is positive):\n"
847
  f"Sum(F_x) = 0 => -R_A + P - R_C = 0\n"
848
+ f"R_A + R_C = {load_P} {unit_P} -(Equation 1)\n\n"
849
 
850
  f"**Step 2:** Equation of Compatibility\n"
851
  f"Since the rod is fixed between unyielding supports, the total deformation must be zero.\n"
 
855
  f"Using the deformation formula delta = PL/AE:\n"
856
  f"(R_A * L_AB) / (A * E) - (R_C * L_BC) / (A * E) = 0\n"
857
  f"Since A and E are constant, they cancel out:\n"
858
+ f"R_A * L_AB = R_C * L_BC -(Equation 2)\n\n"
859
 
860
  f"**Step 3:** Solve for the Reaction Forces\n"
861
  f"From Equation 1, we can express R_C as: R_C = {load_P} - R_A.\n"
data/templates/branches/mechanical_engineering/mechanics_of_materials/torsion.py CHANGED
@@ -2,6 +2,7 @@ import random
2
  import math
3
  from data.templates.branches.mechanical_engineering.constants import SHEAR_MODULUS_VALUES
4
 
 
5
  # Template 1 (Easy)
6
  def template_shear_stress_torsion():
7
  """
@@ -10,8 +11,7 @@ def template_shear_stress_torsion():
10
  Scenario:
11
  This template generates a foundational problem testing the ability to calculate
12
  the polar moment of inertia (J) for a solid or hollow circular shaft.
13
- It then uses this value to determine the maximum shearing stress (tau_max)
14
- resulting from an applied torque.
15
 
16
  Core Equations:
17
  tau_max = (T * c) / J
@@ -30,11 +30,11 @@ def template_shear_stress_torsion():
30
  # Ensure diameters are distinct and practical
31
  d_outer = random.randint(30, 150) # Outer diameter in mm
32
 
33
- # For hollow shafts, ensure the inner diameter is smaller than the outer
34
  if shaft_type == 'hollow':
35
- # Ensure inner diameter is at least 10mm smaller to be meaningful
36
- max_inner_dia = max(20, d_outer - 10)
37
- d_inner = random.randint(20, max_inner_dia)
38
  else:
39
  d_inner = 0
40
 
@@ -70,10 +70,10 @@ def template_shear_stress_torsion():
70
 
71
  # Construct the part of the question describing the geometry
72
  if shaft_type == 'solid':
73
- geometry_desc = f"a solid circular shaft with an outer diameter of {d_outer} mm"
74
  else: # shaft_type == 'hollow'
75
  geometry_desc = (
76
- f"a hollow circular shaft with an outer diameter of {d_outer} mm "
77
  f"and an inner diameter of {d_inner} mm"
78
  )
79
 
@@ -109,6 +109,7 @@ def template_shear_stress_torsion():
109
 
110
  f"**Step 3:** Apply the torsion formula to find the maximum shearing stress (tau_max).\n"
111
  f"The formula is: tau_max = (T * c) / J\n"
 
112
  f"T = {torque} N.m\n"
113
  f"c = {c_outer} m\n"
114
  f"J = {polar_moment_J:.3e} m^4\n"
@@ -349,37 +350,46 @@ def template_composite_shafts_series():
349
  - str: A question about a composite shaft in series.
350
  - str: A step-by-step solution.
351
  """
352
- # 1. Parameterize the inputs with random values
353
- torque = round(random.uniform(200.0, 7500.0), 1)
 
 
 
 
 
 
 
 
 
354
 
355
  # Properties for Segment 1 (AB)
356
  l1 = round(random.uniform(0.5, 2.5), 2)
357
  d1 = random.randint(40, 120)
358
- mat1_name, g1_gpa = random.choice(list(SHEAR_MODULUS_VALUES.items()))
359
 
360
  # Properties for Segment 2 (BC)
361
  l2 = round(random.uniform(0.5, 2.5), 2)
362
- d2 = random.randint(30, d1) # Ensure d2 is not larger than d1 for a typical stepped shaft
363
- mat2_name, g2_gpa = random.choice(list(SHEAR_MODULUS_VALUES.items()))
364
 
365
  # Standardize precision for final outputs
366
  precision = 4
367
 
368
  # 2. Perform the core calculations
369
 
370
- # --- Calculations for Segment 1 (AB) ---
371
  c1_m = d1 / 2000.0
372
  g1_pa = g1_gpa * 1e9
373
  j1 = (math.pi / 2) * (c1_m ** 4)
374
  phi1_rad = (torque * l1) / (j1 * g1_pa)
375
 
376
- # --- Calculations for Segment 2 (BC) ---
377
  c2_m = d2 / 2000.0
378
  g2_pa = g2_gpa * 1e9
379
  j2 = (math.pi / 2) * (c2_m ** 4)
380
  phi2_rad = (torque * l2) / (j2 * g2_pa)
381
 
382
- # --- Total Angle of Twist ---
383
  phi_total_rad = phi1_rad + phi2_rad
384
  phi_total_deg = math.degrees(phi_total_rad)
385
 
@@ -403,7 +413,8 @@ def template_composite_shafts_series():
403
  f"**Step 1:** Analyze the System\n"
404
  f"The shaft is fixed at A, so the torque T = {torque} N.m is transmitted through both segments AB and BC. "
405
  f"The total angle of twist at C is the sum of the twist in segment AB and the twist in segment BC.\n"
406
- f"phi_total = phi_AB + phi_BC\n\n"
 
407
 
408
  f"**Step 2:** Calculate Angle of Twist for Segment AB (phi_AB)\n"
409
  f"Convert units: c1 = {d1}/2 mm = {c1_m} m; G1 = {g1_gpa} GPa = {g1_pa:.2e} Pa\n"
@@ -440,43 +451,43 @@ def template_statically_indeterminate_shaft():
440
  This problem deals with a shaft that is fixed at both ends and has a torque
441
  applied at an intermediate point. Since there are two unknown reaction torques
442
  but only one static equilibrium equation, the problem is statically
443
- indeterminate. It must be solved by considering both static equilibrium and
444
- the geometric compatibility of deformation (i.e., the total angle of twist is zero).
445
 
446
  Core Equations:
447
  1. Statics: T_A + T_B = T_applied
448
- 2. Compatibility: phi_AC + phi_CB = 0 => (T_A * L_AC) / (JG) = (T_B * L_BC) / (JG)
449
- This simplifies to: T_A * L_AC = T_B * L_BC
450
 
451
  Returns:
452
  tuple: A tuple containing:
453
  - str: A question asking for the reaction torques.
454
  - str: A step-by-step solution using statics and compatibility.
455
  """
456
- # 1. Parameterize the inputs with random values
457
- total_length = round(random.uniform(2.0, 6.0), 2)
458
- applied_torque = round(random.uniform(1000.0, 15000.0), -2)
459
- diameter = random.randint(50, 150)
 
 
460
 
461
  # Ensure the torque is applied at a non-trivial location
462
  pos_L_AC = round(random.uniform(0.2 * total_length, 0.8 * total_length), 2)
463
  pos_L_BC = total_length - pos_L_AC
464
 
465
- material_name, shear_modulus_gpa = random.choice(list(SHEAR_MODULUS_VALUES.items()))
 
 
 
 
 
 
 
466
 
467
  # Standardize precision for final outputs
468
  precision = 2
469
 
470
  # 2. Perform the core calculations
471
- # Based on the system of equations:
472
- # (1) T_A + T_B = T_applied
473
- # (2) T_A * L_AC = T_B * L_BC => T_B = T_A * (L_AC / L_BC)
474
- # Substitute (2) into (1):
475
- # T_A + T_A * (L_AC / L_BC) = T_applied
476
- # T_A * (1 + L_AC/L_BC) = T_applied
477
- # T_A * ( (L_BC + L_AC) / L_BC ) = T_applied
478
- # T_A * (L / L_BC) = T_applied
479
  # T_A = T_applied * (L_BC / L)
 
480
 
481
  reaction_torque_A = applied_torque * (pos_L_BC / total_length)
482
  reaction_torque_B = applied_torque * (pos_L_AC / total_length)
@@ -499,39 +510,35 @@ def template_statically_indeterminate_shaft():
499
 
500
  f"**Analysis:**\n"
501
  f"This problem is statically indeterminate because there are two unknown reaction torques (T_A and T_B) "
502
- f"and only one relevant equation from statics. We need an additional equation from the deformation of the shaft.\n\n"
 
503
 
504
  f"**Step 1:** Statics Equilibrium Equation\n"
505
- f"For the shaft to be in rotational equilibrium, the sum of all torques must be zero. Let's assume T_A and T_B act in the opposite direction to T_applied.\n"
506
  f"(1) T_A + T_B = T_applied = {int(applied_torque)} N.m\n\n"
507
 
508
  f"**Step 2:** Compatibility Equation\n"
509
- f"Since the shaft is fixed at both ends, the total angle of twist from A to B must be zero. The twist from A to C and the twist from C to B must cancel each other out.\n"
510
- f"phi_A_to_B = phi_AC + phi_CB = 0\n"
511
- f"The torque in section AC is T_A. The torque in section CB is T_applied - T_A = T_B. For our compatibility equation, it is easier to think of the torque in CB as T_B acting from the other direction.\n"
512
- f"phi_AC = (T_A * L_AC) / (J*G)\n"
513
- f"phi_CB = -(T_B * L_BC) / (J*G) (The negative sign indicates it twists in the opposite direction)\n"
514
- f"(T_A * L_AC) / (J*G) - (T_B * L_BC) / (J*G) = 0\n"
515
- f"The terms J and G are constant for the shaft and cancel out, leaving a relationship between the torques and lengths:\n"
516
  f"(2) T_A * L_AC = T_B * L_BC\n\n"
517
 
518
  f"**Step 3:** Solve the System of Two Equations\n"
519
- f"We have two equations:\n"
520
- f"(1) T_A + T_B = {int(applied_torque)}\n"
521
- f"(2) T_A * {pos_L_AC} = T_B * {round(pos_L_BC, 2)}\n"
522
- f"From equation (2), we can express T_B in terms of T_A:\n"
523
- f"T_B = T_A * ({pos_L_AC} / {round(pos_L_BC, 2)})\n"
524
  f"Substitute this into equation (1):\n"
525
- f"T_A + T_A * ({pos_L_AC} / {round(pos_L_BC, 2)}) = {int(applied_torque)}\n"
526
- f"T_A * (1 + {round(pos_L_AC / pos_L_BC, 3)}) = {int(applied_torque)}\n"
527
- f"T_A = {int(applied_torque)} / {round(1 + (pos_L_AC / pos_L_BC), 3)} = {round(reaction_torque_A, precision)} N.m\n"
528
- f"Now find T_B using equation (1):\n"
529
- f"T_B = {int(applied_torque)} - T_A = {int(applied_torque)} - {round(reaction_torque_A, precision)} = {round(reaction_torque_B, precision)} N.m\n\n"
530
 
531
  f"**Answer:**\n"
532
  f"The reaction torques at the supports are:\n"
533
- f"T_A = {round(reaction_torque_A, precision)} N.m\n"
534
- f"T_B = {round(reaction_torque_B, precision)} N.m"
535
  )
536
 
537
  return question, solution
 
2
  import math
3
  from data.templates.branches.mechanical_engineering.constants import SHEAR_MODULUS_VALUES
4
 
5
+
6
  # Template 1 (Easy)
7
  def template_shear_stress_torsion():
8
  """
 
11
  Scenario:
12
  This template generates a foundational problem testing the ability to calculate
13
  the polar moment of inertia (J) for a solid or hollow circular shaft.
14
+ It then uses this value to determine the maximum shearing stress (tau_max).
 
15
 
16
  Core Equations:
17
  tau_max = (T * c) / J
 
30
  # Ensure diameters are distinct and practical
31
  d_outer = random.randint(30, 150) # Outer diameter in mm
32
 
33
+ # For hollow shafts, ensure realistic wall thickness
34
  if shaft_type == 'hollow':
35
+ # Inner diameter is 50-80% of outer diameter (realistic proportions)
36
+ ratio = random.uniform(0.5, 0.8)
37
+ d_inner = round(d_outer * ratio)
38
  else:
39
  d_inner = 0
40
 
 
70
 
71
  # Construct the part of the question describing the geometry
72
  if shaft_type == 'solid':
73
+ geometry_desc = f"solid circular shaft with an outer diameter of {d_outer} mm"
74
  else: # shaft_type == 'hollow'
75
  geometry_desc = (
76
+ f"hollow circular shaft with an outer diameter of {d_outer} mm "
77
  f"and an inner diameter of {d_inner} mm"
78
  )
79
 
 
109
 
110
  f"**Step 3:** Apply the torsion formula to find the maximum shearing stress (tau_max).\n"
111
  f"The formula is: tau_max = (T * c) / J\n"
112
+ f"\n"
113
  f"T = {torque} N.m\n"
114
  f"c = {c_outer} m\n"
115
  f"J = {polar_moment_J:.3e} m^4\n"
 
350
  - str: A question about a composite shaft in series.
351
  - str: A step-by-step solution.
352
  """
353
+ # 1. Parameterize the inputs with physically realistic values
354
+
355
+ # Reduce max torque to prevent plastic deformation/failure
356
+ torque = round(random.uniform(200.0, 3000.0), 1)
357
+
358
+ # Filter for strong materials (Metals, G > 20 GPa) to ensure elastic behavior
359
+ STRONG_MATERIALS = {k: v for k, v in SHEAR_MODULUS_VALUES.items() if v > 20.0}
360
+
361
+ # Fallback if dictionary is empty
362
+ if not STRONG_MATERIALS:
363
+ STRONG_MATERIALS = {"Structural Steel": 79.3, "Aluminum Alloy": 26.0}
364
 
365
  # Properties for Segment 1 (AB)
366
  l1 = round(random.uniform(0.5, 2.5), 2)
367
  d1 = random.randint(40, 120)
368
+ mat1_name, g1_gpa = random.choice(list(STRONG_MATERIALS.items()))
369
 
370
  # Properties for Segment 2 (BC)
371
  l2 = round(random.uniform(0.5, 2.5), 2)
372
+ d2 = random.randint(30, d1) # Ensure d2 is not larger than d1
373
+ mat2_name, g2_gpa = random.choice(list(STRONG_MATERIALS.items()))
374
 
375
  # Standardize precision for final outputs
376
  precision = 4
377
 
378
  # 2. Perform the core calculations
379
 
380
+ # Calculations for Segment 1 (AB)
381
  c1_m = d1 / 2000.0
382
  g1_pa = g1_gpa * 1e9
383
  j1 = (math.pi / 2) * (c1_m ** 4)
384
  phi1_rad = (torque * l1) / (j1 * g1_pa)
385
 
386
+ # Calculations for Segment 2 (BC)
387
  c2_m = d2 / 2000.0
388
  g2_pa = g2_gpa * 1e9
389
  j2 = (math.pi / 2) * (c2_m ** 4)
390
  phi2_rad = (torque * l2) / (j2 * g2_pa)
391
 
392
+ # Total Angle of Twist
393
  phi_total_rad = phi1_rad + phi2_rad
394
  phi_total_deg = math.degrees(phi_total_rad)
395
 
 
413
  f"**Step 1:** Analyze the System\n"
414
  f"The shaft is fixed at A, so the torque T = {torque} N.m is transmitted through both segments AB and BC. "
415
  f"The total angle of twist at C is the sum of the twist in segment AB and the twist in segment BC.\n"
416
+ f"phi_total = phi_AB + phi_BC\n"
417
+ f"\n\n"
418
 
419
  f"**Step 2:** Calculate Angle of Twist for Segment AB (phi_AB)\n"
420
  f"Convert units: c1 = {d1}/2 mm = {c1_m} m; G1 = {g1_gpa} GPa = {g1_pa:.2e} Pa\n"
 
451
  This problem deals with a shaft that is fixed at both ends and has a torque
452
  applied at an intermediate point. Since there are two unknown reaction torques
453
  but only one static equilibrium equation, the problem is statically
454
+ indeterminate.
 
455
 
456
  Core Equations:
457
  1. Statics: T_A + T_B = T_applied
458
+ 2. Compatibility: T_A * L_AC = T_B * L_BC
 
459
 
460
  Returns:
461
  tuple: A tuple containing:
462
  - str: A question asking for the reaction torques.
463
  - str: A step-by-step solution using statics and compatibility.
464
  """
465
+ # 1. Parameterize the inputs with physically realistic values
466
+
467
+ total_length = round(random.uniform(2.0, 5.0), 2)
468
+ # Reduce max torque to 5000 Nm to ensure elastic behavior for given diameters
469
+ applied_torque = round(random.uniform(500.0, 5000.0), -2)
470
+ diameter = random.randint(50, 150) # mm
471
 
472
  # Ensure the torque is applied at a non-trivial location
473
  pos_L_AC = round(random.uniform(0.2 * total_length, 0.8 * total_length), 2)
474
  pos_L_BC = total_length - pos_L_AC
475
 
476
+ # FILTER: Select only strong materials (Metals, G > 20 GPa) to avoid failure
477
+ # This prevents assigning 5000 Nm of torque to a Nylon shaft.
478
+ strong_materials = {k: v for k, v in SHEAR_MODULUS_VALUES.items() if v > 20.0}
479
+ if not strong_materials:
480
+ # Fallback if dictionary is empty or has no metals
481
+ material_name, shear_modulus_gpa = "Structural Steel", 79.3
482
+ else:
483
+ material_name, shear_modulus_gpa = random.choice(list(strong_materials.items()))
484
 
485
  # Standardize precision for final outputs
486
  precision = 2
487
 
488
  # 2. Perform the core calculations
 
 
 
 
 
 
 
 
489
  # T_A = T_applied * (L_BC / L)
490
+ # T_B = T_applied * (L_AC / L)
491
 
492
  reaction_torque_A = applied_torque * (pos_L_BC / total_length)
493
  reaction_torque_B = applied_torque * (pos_L_AC / total_length)
 
510
 
511
  f"**Analysis:**\n"
512
  f"This problem is statically indeterminate because there are two unknown reaction torques (T_A and T_B) "
513
+ f"and only one relevant equation from statics. We need an additional equation from the deformation of the shaft.\n"
514
+ f"\n\n"
515
 
516
  f"**Step 1:** Statics Equilibrium Equation\n"
517
+ f"For the shaft to be in rotational equilibrium, the sum of all torques must be zero.\n"
518
  f"(1) T_A + T_B = T_applied = {int(applied_torque)} N.m\n\n"
519
 
520
  f"**Step 2:** Compatibility Equation\n"
521
+ f"Since the shaft is fixed at both ends, the total angle of twist from A to B must be zero. "
522
+ f"The twist caused by T_A acting on length AC must equal the twist caused by T_B acting on length BC.\n"
523
+ f"phi_AC = phi_CB\n"
524
+ f"(T_A * L_AC) / (J*G) = (T_B * L_BC) / (J*G)\n"
525
+ f"Since J and G are constant, they cancel out:\n"
 
 
526
  f"(2) T_A * L_AC = T_B * L_BC\n\n"
527
 
528
  f"**Step 3:** Solve the System of Two Equations\n"
529
+ f"From equation (2), express T_B in terms of T_A:\n"
530
+ f"T_B = T_A * ({pos_L_AC} / {pos_L_BC:.2f})\n"
 
 
 
531
  f"Substitute this into equation (1):\n"
532
+ f"T_A + T_A * ({pos_L_AC} / {pos_L_BC:.2f}) = {int(applied_torque)}\n"
533
+ f"T_A * (1 + {pos_L_AC/pos_L_BC:.3f}) = {int(applied_torque)}\n"
534
+ f"T_A = {int(applied_torque)} / {1 + (pos_L_AC / pos_L_BC):.3f} = {reaction_torque_A:.{precision}f} N.m\n"
535
+ f"Now find T_B:\n"
536
+ f"T_B = {int(applied_torque)} - {reaction_torque_A:.{precision}f} = {reaction_torque_B:.{precision}f} N.m\n\n"
537
 
538
  f"**Answer:**\n"
539
  f"The reaction torques at the supports are:\n"
540
+ f"T_A = {reaction_torque_A:.{precision}f} N.m\n"
541
+ f"T_B = {reaction_torque_B:.{precision}f} N.m"
542
  )
543
 
544
  return question, solution
data/templates/branches/mechanical_engineering/vibrations_and_acoustics/__pycache__/harmonically_excited_vibrations.cpython-311.pyc ADDED
Binary file (27.8 kB). View file
 
data/templates/branches/mechanical_engineering/vibrations_and_acoustics/__pycache__/single_degree_of_freedom_systems.cpython-311.pyc ADDED
Binary file (26 kB). View file
 
src/template_loader.py CHANGED
@@ -3,67 +3,94 @@ import inspect
3
  import importlib.util
4
  from pathlib import Path
5
 
6
- # Define root path relative to this script
7
- BASE_DIR = Path(__file__).parent.parent / "data" / "templates" / "branches"
 
 
 
 
 
8
 
9
  def get_branches():
10
- """Returns a list of available engineering branches."""
11
- if not BASE_DIR.exists():
12
- return []
13
  return sorted([d.name for d in BASE_DIR.iterdir() if d.is_dir() and not d.name.startswith("__")])
14
 
15
  def get_areas(branch_name):
16
- """Returns the sub-areas (directories) for a given branch."""
17
  branch_path = BASE_DIR / branch_name
18
- if not branch_path.exists():
19
- return []
20
  return sorted([d.name for d in branch_path.iterdir() if d.is_dir() and not d.name.startswith("__")])
21
 
22
  def get_template_files(branch_name, area_name):
23
- """Returns the python filenames (modules) in an area."""
24
  area_path = BASE_DIR / branch_name / area_name
25
- if not area_path.exists():
26
- return []
27
-
28
- files = [
29
- f.stem for f in area_path.glob("*.py")
30
- if f.name not in ["constants.py", "__init__.py"] and not f.name.startswith("__")
31
- ]
32
- return sorted(files)
33
 
34
  def load_template_functions(branch, area, filename):
35
- """
36
- Loads a specific file and returns a list of its template functions.
37
- Returns: List of tuples (function_name, function_object)
38
- """
39
  file_path = BASE_DIR / branch / area / f"{filename}.py"
40
- if not file_path.exists():
41
- raise FileNotFoundError(f"File not found: {file_path}")
 
 
 
 
42
 
43
- # Dynamic import
44
  spec = importlib.util.spec_from_file_location(filename, file_path)
45
  module = importlib.util.module_from_spec(spec)
46
 
47
- # Add branch folder to path so imports like 'from constants import...' work
48
- sys.path.append(str(file_path.parent.parent))
49
-
50
  try:
51
  spec.loader.exec_module(module)
52
  except Exception as e:
53
- return [] # Return empty if module crashes on load
 
54
 
55
- # Extract all functions that start with 'template_'
56
  templates = []
57
  for name, obj in inspect.getmembers(module):
58
  if inspect.isfunction(obj) and name.startswith("template_"):
59
  templates.append((name, obj))
60
-
61
  return sorted(templates, key=lambda x: x[0])
62
 
63
- def get_source_code(branch, area, filename):
64
- """Returns the raw source code of the entire file."""
65
- file_path = BASE_DIR / branch / area / f"{filename}.py"
66
- if not file_path.exists():
67
- return "# Error: File not found."
68
- with open(file_path, "r", encoding="utf-8") as f:
69
- return f.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import importlib.util
4
  from pathlib import Path
5
 
6
+ # PATH CONFIGURATION
7
+ # 1. Get the folder where this script is (e.g., EngChain/src)
8
+ CURRENT_DIR = Path(__file__).resolve().parent
9
+ # 2. Get the project root (e.g., EngChain)
10
+ PROJECT_ROOT = CURRENT_DIR.parent
11
+ # 3. Define where templates live
12
+ BASE_DIR = PROJECT_ROOT / "data" / "templates" / "branches"
13
 
14
  def get_branches():
15
+ if not BASE_DIR.exists(): return []
 
 
16
  return sorted([d.name for d in BASE_DIR.iterdir() if d.is_dir() and not d.name.startswith("__")])
17
 
18
  def get_areas(branch_name):
 
19
  branch_path = BASE_DIR / branch_name
20
+ if not branch_path.exists(): return []
 
21
  return sorted([d.name for d in branch_path.iterdir() if d.is_dir() and not d.name.startswith("__")])
22
 
23
  def get_template_files(branch_name, area_name):
 
24
  area_path = BASE_DIR / branch_name / area_name
25
+ if not area_path.exists(): return []
26
+ return sorted([f.stem for f in area_path.glob("*.py") if f.name not in ["constants.py", "__init__.py"] and not f.name.startswith("__")])
 
 
 
 
 
 
27
 
28
  def load_template_functions(branch, area, filename):
 
 
 
 
29
  file_path = BASE_DIR / branch / area / f"{filename}.py"
30
+ if not file_path.exists(): raise FileNotFoundError(f"File not found: {file_path}")
31
+
32
+ # CRITICAL FIX: Add Project Root to sys.path
33
+ # This ensures 'from data...' imports work, preventing the silent crash
34
+ if str(PROJECT_ROOT) not in sys.path:
35
+ sys.path.append(str(PROJECT_ROOT))
36
 
 
37
  spec = importlib.util.spec_from_file_location(filename, file_path)
38
  module = importlib.util.module_from_spec(spec)
39
 
 
 
 
40
  try:
41
  spec.loader.exec_module(module)
42
  except Exception as e:
43
+ print(f"!!! Error loading module '{filename}': {e}")
44
+ return []
45
 
 
46
  templates = []
47
  for name, obj in inspect.getmembers(module):
48
  if inspect.isfunction(obj) and name.startswith("template_"):
49
  templates.append((name, obj))
 
50
  return sorted(templates, key=lambda x: x[0])
51
 
52
+ def get_source_code(func_object):
53
+ """Extracts ONLY the function code, not the whole file."""
54
+ try:
55
+ return inspect.getsource(func_object)
56
+ except OSError:
57
+ return "# Error: Source not available."
58
+
59
+
60
+ if __name__ == "__main__":
61
+ print(" 1. Testing get_branches ")
62
+ branches = get_branches()
63
+ print(f"Branches found: {branches}")
64
+
65
+ if branches:
66
+ # Test with the first available branch
67
+ selected_branch = branches[0]
68
+ print(f"\n 2. Testing get_areas for '{selected_branch}' ")
69
+ areas = get_areas(selected_branch)
70
+ print(f"Areas found: {areas}")
71
+
72
+ if areas:
73
+ # Test with the first available area
74
+ selected_area = areas[0]
75
+ print(f"\n 3. Testing get_template_files for '{selected_area}' ")
76
+ files = get_template_files(selected_branch, selected_area)
77
+ print(f"Files found: {files}")
78
+
79
+ if files:
80
+ # Test with the first available file
81
+ selected_file = files[1]
82
+
83
+ print(f"\n 4. Testing load_template_functions for '{selected_file}' ")
84
+ funcs = load_template_functions(selected_branch, selected_area, selected_file)
85
+ func_names = [f[0] for f in funcs]
86
+ print(f"Functions found: {func_names}")
87
+
88
+ if funcs:
89
+ # Test getting source code for the FIRST function found
90
+ first_func_name, first_func_obj = funcs[2]
91
+ print(f"\n 5. Testing get_source_code for '{first_func_name}' ")
92
+
93
+ code = get_source_code(first_func_obj)
94
+ print(f"Source code preview:\n{code}...")
95
+ else:
96
+ print("\n 5. Skipped (No functions found to extract code from) ")