Usmansafdarktk commited on
Commit
b9bf0fc
·
0 Parent(s):

Deploy EngChain annotator

Browse files
Files changed (42) hide show
  1. README.md +9 -0
  2. app.py +255 -0
  3. data/templates/branches/chemical_engineering/__pycache__/constants.cpython-311.pyc +0 -0
  4. data/templates/branches/chemical_engineering/constants.py +629 -0
  5. data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/conversion_and_reactor_sizing.cpython-311.pyc +0 -0
  6. data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/mole_balances.cpython-311.pyc +0 -0
  7. data/templates/branches/chemical_engineering/reaction_kinetics/conversion_and_reactor_sizing.py +311 -0
  8. data/templates/branches/chemical_engineering/reaction_kinetics/mole_balances.py +431 -0
  9. data/templates/branches/chemical_engineering/reaction_kinetics/stoichiometry.py +565 -0
  10. data/templates/branches/chemical_engineering/thermodynamics/heat_effects.py +476 -0
  11. data/templates/branches/chemical_engineering/thermodynamics/volumetric_properties_pure_fluids.py +701 -0
  12. data/templates/branches/chemical_engineering/transport_phenomena/shell_momentum_balances.py +339 -0
  13. data/templates/branches/chemical_engineering/transport_phenomena/viscosity_and_momentum_transport.py +514 -0
  14. data/templates/branches/electrical_engineering/__pycache__/constants.cpython-311.pyc +0 -0
  15. data/templates/branches/electrical_engineering/constants.py +67 -0
  16. data/templates/branches/electrical_engineering/digital_communications/__pycache__/deterministic_and_random_signal_analysis.cpython-311.pyc +0 -0
  17. data/templates/branches/electrical_engineering/digital_communications/__pycache__/digital_modulation_schemes.cpython-311.pyc +0 -0
  18. data/templates/branches/electrical_engineering/digital_communications/deterministic_and_random_signal_analysis.py +570 -0
  19. data/templates/branches/electrical_engineering/digital_communications/digital_modulation_schemes.py +664 -0
  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/electromagnetics_and_waves/electrostatics.py +566 -0
  24. data/templates/branches/electrical_engineering/electromagnetics_and_waves/magnetostatics.py +168 -0
  25. data/templates/branches/electrical_engineering/electromagnetics_and_waves/waves_and_phasors.py +650 -0
  26. data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/continuous_time_signals.cpython-311.pyc +0 -0
  27. data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/discrete_time_signals.cpython-311.pyc +0 -0
  28. data/templates/branches/electrical_engineering/signals_and_systems/continuous_time_signals.py +621 -0
  29. data/templates/branches/electrical_engineering/signals_and_systems/discrete_time_signals.py +817 -0
  30. data/templates/branches/mechanical_engineering/constants.py +304 -0
  31. data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_kinematics.py +734 -0
  32. data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_statics.py +616 -0
  33. data/templates/branches/mechanical_engineering/mechanics_of_materials/stress_and_strain.py +916 -0
  34. data/templates/branches/mechanical_engineering/mechanics_of_materials/torsion.py +604 -0
  35. data/templates/branches/mechanical_engineering/vibrations_and_acoustics/harmonically_excited_vibrations.py +584 -0
  36. data/templates/branches/mechanical_engineering/vibrations_and_acoustics/single_degree_of_freedom_systems.py +655 -0
  37. requirements.txt +4 -0
  38. src/__pycache__/storage.cpython-311.pyc +0 -0
  39. src/__pycache__/template_loader.cpython-311.pyc +0 -0
  40. src/storage.py +46 -0
  41. src/template_loader.py +69 -0
  42. test_loader.py +78 -0
README.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: EngChain Annotator
3
+ emoji: 🛡️
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: streamlit
7
+ python_version: "3.11"
8
+ sdk_version: "1.25.0"
9
+ ---
app.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import traceback
3
+ import random
4
+ from src.template_loader import (
5
+ get_branches,
6
+ get_areas,
7
+ get_template_files,
8
+ load_template_functions,
9
+ get_source_code
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.
30
+ Returns: List of dicts {'branch', 'area', 'file', 'name', 'func'}
31
+ """
32
+ queue = []
33
+ areas = get_areas(branch)
34
+
35
+ progress_bar = st.progress(0)
36
+ status_text = st.empty()
37
+
38
+ total_steps = len(areas)
39
+ for i, area in enumerate(areas):
40
+ status_text.text(f"Loading area: {area}...")
41
+ files = get_template_files(branch, area)
42
+ for file in files:
43
+ # Load all functions from this file
44
+ try:
45
+ funcs = load_template_functions(branch, area, file)
46
+ for func_name, func_obj in funcs:
47
+ queue.append({
48
+ "branch": branch,
49
+ "area": area,
50
+ "file": file,
51
+ "name": func_name,
52
+ "func": func_obj
53
+ })
54
+ except Exception as e:
55
+ print(f"Error loading {file}: {e}")
56
+ progress_bar.progress((i + 1) / total_steps)
57
+
58
+ status_text.empty()
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:
66
+ st.session_state["review_queue"] = []
67
+ if "current_index" not in st.session_state:
68
+ st.session_state["current_index"] = 0
69
+ if "annotator_name" not in st.session_state:
70
+ st.session_state["annotator_name"] = ""
71
+ if "current_branch" not in st.session_state:
72
+ st.session_state["current_branch"] = ""
73
+ if "review_submitted" not in st.session_state:
74
+ st.session_state["review_submitted"] = False
75
+
76
+ # ==========================================
77
+ # VIEW 1: LANDING PAGE
78
+ # ==========================================
79
+ if st.session_state["app_mode"] == "landing":
80
+ st.title("🛡️ EngChain Verification Portal")
81
+
82
+ st.markdown("""
83
+ ### Welcome, Expert Annotator!
84
+ Thank you for contributing to **EngChain**. Your task is to verify the correctness and quality
85
+ of our symbolic engineering templates.
86
+
87
+ **Instructions:**
88
+ 1. Select your Engineering Domain.
89
+ 2. Enter your Name/ID.
90
+ 3. You will be guided through templates one-by-one.
91
+ 4. For each template, check the **Code** and the **Generated Trace**.
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")
99
+ col1, col2 = st.columns(2)
100
+
101
+ with col1:
102
+ branches = get_branches()
103
+ branch_input = st.selectbox("Select Your Domain", branches)
104
+
105
+ with col2:
106
+ name_input = st.text_input("Enter Your Name / ID")
107
+
108
+ # Button styling: type="primary" gives it emphasis (color depends on theme, usually red/blue)
109
+ submitted = st.form_submit_button("Start Annotation Session", type="primary")
110
+
111
+ if submitted:
112
+ if not name_input.strip():
113
+ st.error("Please enter your name to proceed.")
114
+ else:
115
+ st.session_state["annotator_name"] = name_input
116
+ st.session_state["current_branch"] = branch_input
117
+
118
+ # Build the queue
119
+ with st.spinner(f"Gathering templates for {branch_input}..."):
120
+ queue = build_review_queue(branch_input)
121
+
122
+ st.session_state["review_queue"] = queue
123
+ st.session_state["current_index"] = 0
124
+ st.session_state["app_mode"] = "review"
125
+ st.rerun()
126
+
127
+ # ==========================================
128
+ # VIEW 2: REVIEW WORKSPACE
129
+ # ==========================================
130
+ elif st.session_state["app_mode"] == "review":
131
+
132
+ queue = st.session_state["review_queue"]
133
+ idx = st.session_state["current_index"]
134
+
135
+ # Check if we are done
136
+ if idx >= len(queue):
137
+ st.session_state["app_mode"] = "done"
138
+ st.rerun()
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
151
+ st.sidebar.markdown("**📂 Area:**")
152
+ st.sidebar.info(current_item['area'])
153
+
154
+ st.sidebar.markdown("**📄 File:**")
155
+ st.sidebar.info(current_item['file'])
156
+
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:
171
+ col_gen, _ = st.columns([1, 4])
172
+ # Renamed button for clarity
173
+ if col_gen.button("Generate New Random Instance"):
174
+ pass # Rerun trigger to get new random numbers
175
+
176
+ try:
177
+ question, solution = current_item['func']()
178
+ st.markdown("#### Question")
179
+ st.info(question)
180
+ st.markdown("#### Solution Trace")
181
+ st.success(solution)
182
+ except Exception as e:
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
192
+ with st.container():
193
+ # Check if already submitted for this specific template
194
+ is_submitted = st.session_state.get("review_submitted", False)
195
+
196
+ if not is_submitted:
197
+ # FORM STATE
198
+ with st.form("audit_form"):
199
+ c1, c2, c3 = st.columns(3)
200
+ phys = c1.slider("Physical Plausibility", 1, 5, 5)
201
+ math = c2.slider("Mathematical Correctness", 1, 5, 5)
202
+ ped = c3.slider("Pedagogical Clarity", 1, 5, 5)
203
+
204
+ decision = st.radio("Certification:", ["Approve", "Reject"], horizontal=True)
205
+ feedback = st.text_area("Feedback (Required for Rejection)", placeholder="Explain any errors found...")
206
+
207
+ # Removed emoji from button
208
+ submit_review = st.form_submit_button("Submit Review")
209
+
210
+ if submit_review:
211
+ if decision == "Reject" and not feedback.strip():
212
+ st.error("Feedback is required for Rejection.")
213
+ else:
214
+ # Save to disk
215
+ save_review(
216
+ st.session_state["annotator_name"],
217
+ current_item["branch"],
218
+ current_item["area"],
219
+ current_item["name"],
220
+ [phys, math, ped],
221
+ decision,
222
+ feedback
223
+ )
224
+ st.success("Review Saved!")
225
+ st.session_state["review_submitted"] = True
226
+ st.rerun()
227
+ else:
228
+ # POST-SUBMISSION STATE (Show Next Button)
229
+ st.success("Review recorded for this template.")
230
+
231
+ # Removed emoji, kept primary type for visibility
232
+ if st.button("Proceed to Next Template", type="primary"):
233
+ st.session_state["current_index"] += 1
234
+ st.session_state["review_submitted"] = False
235
+ st.rerun()
236
+
237
+ # ==========================================
238
+ # VIEW 3: COMPLETION PAGE
239
+ # ==========================================
240
+ elif st.session_state["app_mode"] == "done":
241
+ # Removed balloons()
242
+ st.title("Session Complete")
243
+
244
+ st.success(f"""
245
+ **Thank you, {st.session_state['annotator_name']}!**
246
+
247
+ You have successfully reviewed all **{len(st.session_state['review_queue'])}** templates
248
+ in the **{st.session_state['current_branch']}** domain.
249
+ """)
250
+
251
+ st.info("Your reviews have been saved securely. You may now close this tab.")
252
+
253
+ if st.button("Start New Session"):
254
+ st.session_state.clear()
255
+ st.rerun()
data/templates/branches/chemical_engineering/__pycache__/constants.cpython-311.pyc ADDED
Binary file (18.7 kB). View file
 
data/templates/branches/chemical_engineering/constants.py ADDED
@@ -0,0 +1,629 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Standard: phases referenced at ~25 °C and 1 atm
2
+
3
+ LIQUID_PHASE_REACTANTS = [
4
+ "Ethyl Acetate",
5
+ "Propylene Glycol",
6
+ "Benzene",
7
+ "Toluene",
8
+ "Acetone",
9
+ "Methanol",
10
+ "Ethanol",
11
+ "Isopropanol", # specify "isopropanol (2-propanol)" if needed
12
+ "n-Butanol", # specify isomer as needed
13
+ "Methyl Ethyl Ketone (MEK) / 2-butanone",
14
+ # Formaldehyde is typically a gas at 25°C; aqueous solution is "Formalin (37% aqueous)"
15
+ "Acetic Acid",
16
+ # Phenol is a solid at 25°C (mp ~40.5°C). Include only if working above that temperature.
17
+ "Glycerol",
18
+ "Xylene",
19
+ "Styrene",
20
+ "Aniline",
21
+ "Cyclohexane",
22
+ "Formic Acid",
23
+ "Nitric Acid",
24
+ "Sulfuric Acid",
25
+ "Ethylene Glycol",
26
+ "Diethyl Ether",
27
+ "Tetrahydrofuran (THF)",
28
+ "Chloroform",
29
+ "Acrylonitrile",
30
+ "Dimethylformamide (DMF)",
31
+ "Methyl Methacrylate",
32
+ "Acetic anhydride" # corrected name (a.k.a. ethanoic anhydride)
33
+ ]
34
+
35
+ GAS_PHASE_REACTANTS = [
36
+ "Methane",
37
+ "Ethane",
38
+ "Propane",
39
+ "Ammonia",
40
+ "Ethylene",
41
+ "Sulfur Dioxide",
42
+ "Hydrogen Sulfide",
43
+ "Vinyl Chloride",
44
+ "Butadiene",
45
+ "Hydrogen",
46
+ "Oxygen",
47
+ "Nitrogen",
48
+ "Chlorine",
49
+ "Carbon Monoxide",
50
+ "Carbon Dioxide",
51
+ "Propylene",
52
+ "Butane",
53
+ "Acetylene",
54
+ "Nitric Oxide (NO)",
55
+ "Nitrogen Dioxide (NO2)",
56
+ "Hydrogen Chloride (HCl)", # gas at 1 atm
57
+ "Phosgene",
58
+ "Ethylene Oxide",
59
+ "Isobutane",
60
+ "Formaldehyde", # moved here: gas at 25°C
61
+ "Acetaldehyde" # volatile; bp ~20.2°C → effectively gas at 25°C
62
+ ]
63
+
64
+ BIOCHEMICAL_SUBSTRATES = [
65
+ "Glucose",
66
+ "Sucrose",
67
+ "Lactose",
68
+ "Fructose",
69
+ "Pyruvate",
70
+ "Maltose",
71
+ "Galactose",
72
+ "Starch", # polymeric solid
73
+ "Cellulose", # polymeric solid
74
+ "Xylose",
75
+ "Glutamate", # often used as salts (e.g., sodium glutamate)
76
+ "Alanine",
77
+ "Lactate", # usually as salt
78
+ "Citrate",
79
+ "Acetyl-CoA", # coenzyme (large, charged)
80
+ "Palmitic Acid",
81
+ "Oleic Acid",
82
+ "Triglycerides",
83
+ "Urea",
84
+ "Aspartate"
85
+ ]
86
+
87
+ GENERAL_REACTANTS = LIQUID_PHASE_REACTANTS + GAS_PHASE_REACTANTS + BIOCHEMICAL_SUBSTRATES
88
+
89
+
90
+ PRODUCTS = [
91
+ "Product Alpha",
92
+ "Product Beta",
93
+ "Product Gamma",
94
+ "Product Delta",
95
+ "Product Sigma",
96
+ "Product Omega",
97
+ "Product Theta",
98
+ "Product Lambda",
99
+ "Product Zeta",
100
+ "Product Kappa",
101
+
102
+ "Compound P",
103
+ "Compound Q",
104
+ "Compound R",
105
+ "Compound S",
106
+ "Compound T",
107
+ "Compound V",
108
+ "Compound W",
109
+ "Compound X",
110
+ "Compound Y",
111
+ "Compound Z",
112
+
113
+ "Species I",
114
+ "Species II",
115
+ "Species III",
116
+ "Species IV",
117
+ "Species V",
118
+ "Species VI",
119
+ "Species VII",
120
+ "Species VIII",
121
+
122
+ "Material A",
123
+ "Material B",
124
+ "Material C",
125
+ "Material D",
126
+ "Material E",
127
+ "Material F",
128
+
129
+ "Substance One",
130
+ "Substance Two",
131
+ "Substance Three",
132
+ "Substance Four",
133
+ "Substance Five",
134
+ "Substance Six",
135
+
136
+ "Molecule M",
137
+ "Molecule N",
138
+ "Molecule O",
139
+ "Molecule P",
140
+ "Molecule Q",
141
+ "Molecule R",
142
+
143
+ "Entity 1",
144
+ "Entity 2",
145
+ "Entity 3",
146
+ "Entity 4",
147
+ "Entity 5",
148
+ "Entity 6"
149
+ ]
150
+
151
+
152
+ # A list of common substances used in thermodynamics problems involving phase change.
153
+ THERMO_SUBSTANCES = [
154
+ # Classic working fluids
155
+ "Water",
156
+ "Ammonia",
157
+ "Carbon Dioxide",
158
+ "Sulfur Dioxide",
159
+
160
+ # Hydrocarbons (fuels and refrigerants)
161
+ "Methane",
162
+ "Ethane",
163
+ "Propane",
164
+ "Butane",
165
+ "Isobutane",
166
+ "Pentane",
167
+ "Iso-pentane",
168
+
169
+ # Refrigerants (with ASHRAE designations)
170
+ "Refrigerant-11 (R-11, Trichlorofluoromethane)",
171
+ "Refrigerant-12 (R-12, Dichlorodifluoromethane)",
172
+ "Refrigerant-22 (R-22, Chlorodifluoromethane)",
173
+ "Refrigerant-134a (R-134a, 1,1,1,2-Tetrafluoroethane)",
174
+ "Refrigerant-123 (R-123, Dichlorotrifluoroethane)",
175
+ "Refrigerant-410A (R-410A, blend of difluoromethane and pentafluoroethane)",
176
+
177
+ # Common industrial/organic fluids used in Rankine/Organic Rankine cycles
178
+ "Toluene",
179
+ "Benzene",
180
+ "Ethanol",
181
+ "Methanol",
182
+ "Acetone",
183
+ "n-Hexane",
184
+ "n-Octane",
185
+ "Cyclohexane"
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 = {
192
+ "Methane": {"Tc": 190.6, "Pc": 45.99, "Vc": 99.0, "Zc": 0.286, "omega": 0.012},
193
+ "Ethane": {"Tc": 305.3, "Pc": 48.72, "Vc": 146.0, "Zc": 0.279, "omega": 0.100},
194
+ "Propane": {"Tc": 369.8, "Pc": 42.48, "Vc": 200.0, "Zc": 0.276, "omega": 0.152},
195
+ "n-Butane": {"Tc": 425.1, "Pc": 37.96, "Vc": 255.0, "Zc": 0.274, "omega": 0.200},
196
+ "n-Pentane": {"Tc": 469.7, "Pc": 33.7, "Vc": 311.0, "Zc": 0.269, "omega": 0.251},
197
+ "n-Hexane": {"Tc": 507.6, "Pc": 30.12, "Vc": 368.0, "Zc": 0.264, "omega": 0.301},
198
+ "n-Heptane": {"Tc": 540.2, "Pc": 27.36, "Vc": 426.0, "Zc": 0.263, "omega": 0.350},
199
+ "n-Octane": {"Tc": 568.8, "Pc": 24.86, "Vc": 492.0, "Zc": 0.259, "omega": 0.400},
200
+ "Ethylene": {"Tc": 282.4, "Pc": 50.42, "Vc": 131.0, "Zc": 0.281, "omega": 0.087},
201
+ "Propylene": {"Tc": 365.0, "Pc": 46.0, "Vc": 181.0, "Zc": 0.275, "omega": 0.140},
202
+ "Benzene": {"Tc": 562.2, "Pc": 48.95, "Vc": 259.0, "Zc": 0.271, "omega": 0.210},
203
+ "Toluene": {"Tc": 591.8, "Pc": 41.09, "Vc": 316.0, "Zc": 0.264, "omega": 0.263},
204
+ "p-Xylene": {"Tc": 616.2, "Pc": 35.12, "Vc": 379.0, "Zc": 0.260, "omega": 0.321},
205
+ "Methanol": {"Tc": 512.6, "Pc": 80.84, "Vc": 118.0, "Zc": 0.224, "omega": 0.564},
206
+ "Ethanol": {"Tc": 513.9, "Pc": 61.37, "Vc": 167.0, "Zc": 0.248, "omega": 0.645},
207
+ "Acetone": {"Tc": 508.2, "Pc": 46.99, "Vc": 209.0, "Zc": 0.232, "omega": 0.304},
208
+ "Water": {"Tc": 647.1, "Pc": 220.64, "Vc": 55.9, "Zc": 0.229, "omega": 0.345},
209
+ "Ammonia": {"Tc": 405.5, "Pc": 113.53, "Vc": 72.5, "Zc": 0.242, "omega": 0.250},
210
+ "Carbon dioxide": {"Tc": 304.2, "Pc": 73.83, "Vc": 94.0, "Zc": 0.274, "omega": 0.224},
211
+ "Carbon monoxide": {"Tc": 132.9, "Pc": 34.99, "Vc": 93.1, "Zc": 0.295, "omega": 0.045},
212
+ "Oxygen": {"Tc": 154.6, "Pc": 50.43, "Vc": 73.4, "Zc": 0.288, "omega": 0.022},
213
+ "Nitrogen": {"Tc": 126.2, "Pc": 33.98, "Vc": 89.8, "Zc": 0.290, "omega": 0.039},
214
+ "Hydrogen": {"Tc": 33.2, "Pc": 12.97, "Vc": 65.0, "Zc": 0.305, "omega": -0.216},
215
+ "Helium": {"Tc": 5.2, "Pc": 2.27, "Vc": 57.8, "Zc": 0.301, "omega": -0.365},
216
+ "Chlorine": {"Tc": 417.0, "Pc": 77.02, "Vc": 124.0, "Zc": 0.275, "omega": 0.090},
217
+ "Sulfur dioxide": {"Tc": 430.8, "Pc": 78.84, "Vc": 122.0, "Zc": 0.269, "omega": 0.245},
218
+ "Hydrogen sulfide": {"Tc": 373.5, "Pc": 89.63, "Vc": 98.6, "Zc": 0.284, "omega": 0.091}
219
+ }
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
+
267
+ # A list of common substances with their molar heats of vaporization (delta_H_vap)
268
+ # at their normal boiling points. All values are in kJ/mol.
269
+ SUBSTANCES_FOR_VAPORIZATION = [
270
+ # Alcohols & Water
271
+ {"name": "Water", "delta_H_vap": 40.66},
272
+ {"name": "Methanol", "delta_H_vap": 35.3},
273
+ {"name": "Ethanol", "delta_H_vap": 38.6},
274
+ {"name": "Isopropanol", "delta_H_vap": 39.85},
275
+
276
+ # Alkanes
277
+ {"name": "Propane", "delta_H_vap": 19.04},
278
+ {"name": "n-Butane", "delta_H_vap": 22.44},
279
+ {"name": "n-Hexane", "delta_H_vap": 28.85},
280
+
281
+ # Organic Solvents
282
+ {"name": "Acetone", "delta_H_vap": 29.1},
283
+ {"name": "Benzene", "delta_H_vap": 30.8},
284
+ {"name": "Toluene", "delta_H_vap": 33.48},
285
+ {"name": "Carbon Tetrachloride", "delta_H_vap": 29.82},
286
+
287
+ # Inorganic & Elemental Substances
288
+ {"name": "Ammonia", "delta_H_vap": 23.3},
289
+ {"name": "Mercury", "delta_H_vap": 59.11},
290
+ {"name": "Nitrogen", "delta_H_vap": 5.57},
291
+ {"name": "Oxygen", "delta_H_vap": 6.82},
292
+ {"name": "Argon", "delta_H_vap": 6.43},
293
+ ]
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,
310
+ "N2(g)": 0,
311
+ "CO(g)": -110.5,
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
+ }
319
+
320
+ # A list of predefined, balanced chemical reactions.
321
+ REACTIONS = [
322
+ {
323
+ "name": "Combustion of Methane",
324
+ "equation": "CH4(g) + 2O2(g) → CO2(g) + 2H2O(l)",
325
+ "reactants": {"CH4(g)": 1, "O2(g)": 2},
326
+ "products": {"CO2(g)": 1, "H2O(l)": 2}
327
+ },
328
+ {
329
+ "name": "Combustion of Propane",
330
+ "equation": "C3H8(g) + 5O2(g) → 3CO2(g) + 4H2O(l)",
331
+ "reactants": {"C3H8(g)": 1, "O2(g)": 5},
332
+ "products": {"CO2(g)": 3, "H2O(l)": 4}
333
+ },
334
+ {
335
+ "name": "Oxidation of Ammonia",
336
+ "equation": "4NH3(g) + 5O2(g) → 4NO(g) + 6H2O(g)",
337
+ "reactants": {"NH3(g)": 4, "O2(g)": 5},
338
+ "products": {"NO(g)": 4, "H2O(g)": 6}
339
+ },
340
+ {
341
+ "name": "Steam Reforming of Methane",
342
+ "equation": "CH4(g) + H2O(g) → CO(g) + 3H2(g)",
343
+ "reactants": {"CH4(g)": 1, "H2O(g)": 1},
344
+ "products": {"CO(g)": 1, "H2(g)": 3}
345
+ }
346
+ ]
347
+
348
+
349
+ # A dictionary of substances with their heat capacity parameters for the
350
+ # equation: Cp/R = A + B*T + C*T² + D*T⁻² where T is in Kelvin.
351
+ # Parameters are typically valid for temperature ranges around 298-1200K
352
+ CP_PARAMS = {
353
+ # Original entries
354
+ "CH4(g)": {"A": 1.702, "B": 9.081E-2, "C": -2.164E-5, "D": 0},
355
+ "CO2(g)": {"A": 5.457, "B": 1.045E-2, "C": 0, "D": -1.157E5},
356
+ "N2(g)": {"A": 3.280, "B": 0.593E-2, "C": 0, "D": 0.040E5},
357
+ "H2O(l)": {"A": 8.712, "B": 1.25E-2, "C": -0.18E-5, "D": 0},
358
+ "C2H5OH(g)": {"A": 3.518, "B": 20.001E-2, "C": -6.002E-5, "D": 0},
359
+
360
+ # Common gases
361
+ "O2(g)": {"A": 3.630, "B": 1.794E-2, "C": -0.658E-5, "D": 0.061E5},
362
+ "H2(g)": {"A": 3.249, "B": 0.422E-2, "C": 0, "D": 0.083E5},
363
+ "NH3(g)": {"A": 3.578, "B": 3.020E-2, "C": 0, "D": -0.186E5},
364
+ "CO(g)": {"A": 3.376, "B": 0.557E-2, "C": 0, "D": -0.031E5},
365
+ "H2S(g)": {"A": 3.931, "B": 1.490E-2, "C": 0, "D": -0.232E5},
366
+
367
+ # Hydrocarbons
368
+ "C2H6(g)": {"A": 1.131, "B": 19.225E-2, "C": -5.561E-5, "D": 0},
369
+ "C3H8(g)": {"A": 1.213, "B": 28.785E-2, "C": -8.824E-5, "D": 0},
370
+ "C4H10(g)": {"A": 1.935, "B": 36.915E-2, "C": -11.402E-5, "D": 0},
371
+ "C2H4(g)": {"A": 1.424, "B": 14.394E-2, "C": -4.392E-5, "D": 0},
372
+ "C2H2(g)": {"A": 6.132, "B": 8.914E-2, "C": -6.347E-5, "D": 0},
373
+
374
+ # Common liquids
375
+ "C6H6(l)": {"A": -0.206, "B": 39.064E-2, "C": -13.301E-5, "D": 0},
376
+ "C7H8(l)": {"A": 0.290, "B": 47.052E-2, "C": -15.716E-5, "D": 0},
377
+ "C3H6O(l)": {"A": 1.506, "B": 30.476E-2, "C": -9.127E-5, "D": 0},
378
+ "CH3OH(l)": {"A": 5.052, "B": 16.561E-2, "C": -3.761E-5, "D": 0},
379
+ "C6H14(l)": {"A": 2.738, "B": 45.854E-2, "C": -14.518E-5, "D": 0},
380
+
381
+ # Inorganic compounds
382
+ "SO2(g)": {"A": 5.699, "B": 0.801E-2, "C": 0, "D": -1.015E5},
383
+ "NO(g)": {"A": 3.387, "B": 0.669E-2, "C": 0, "D": 0.095E5},
384
+ "NO2(g)": {"A": 4.982, "B": 1.195E-2, "C": -0.792E-5, "D": -0.377E5},
385
+ "Cl2(g)": {"A": 4.442, "B": 0.089E-2, "C": 0, "D": -0.344E5},
386
+ "HCl(g)": {"A": 3.156, "B": 0.623E-2, "C": 0, "D": 0.151E5},
387
+
388
+ # Noble gases
389
+ "He(g)": {"A": 2.500, "B": 0, "C": 0, "D": 0},
390
+ "Ar(g)": {"A": 2.500, "B": 0, "C": 0, "D": 0},
391
+ "Ne(g)": {"A": 2.500, "B": 0, "C": 0, "D": 0},
392
+
393
+ # Additional common substances
394
+ "Air(g)": {"A": 3.355, "B": 0.575E-2, "C": 0, "D": -0.016E5},
395
+ "H2O(g)": {"A": 3.470, "B": 1.450E-2, "C": 0, "D": 0.121E5},
396
+ "H2SO4(l)": {"A": 2.850, "B": 13.400E-2, "C": 0, "D": 0},
397
+ "NaCl(s)": {"A": 5.526, "B": 1.963E-2, "C": 0, "D": -0.333E5},
398
+ "CaCO3(s)": {"A": 12.572, "B": 2.637E-2, "C": -3.120E-5, "D": -3.642E5}
399
+ }
400
+
401
+
402
+ # A list of predefined, balanced combustion reactions with theoretical air.
403
+ COMBUSTION_REACTIONS = [
404
+ {
405
+ "name": "Combustion of Methane",
406
+ "fuel": "Methane",
407
+ "equation": "CH4(g) + 2O2(g) + 7.52N2(g) → CO2(g) + 2H2O(g) + 7.52N2(g)",
408
+ "reactants": {"CH4(g)": 1, "O2(g)": 2, "N2(g)": 7.52},
409
+ "products": {"CO2(g)": 1, "H2O(g)": 2, "N2(g)": 7.52}
410
+ },
411
+ {
412
+ "name": "Combustion of Propane",
413
+ "fuel": "Propane",
414
+ "equation": "C3H8(g) + 5O2(g) + 18.8N2(g) → 3CO2(g) + 4H2O(g) + 18.8N2(g)",
415
+ "reactants": {"C3H8(g)": 1, "O2(g)": 5, "N2(g)": 18.8},
416
+ "products": {"CO2(g)": 3, "H2O(g)": 4, "N2(g)": 18.8}
417
+ },
418
+ {
419
+ "name": "Combustion of Hydrogen",
420
+ "fuel": "Hydrogen",
421
+ "equation": "H2(g) + 0.5O2(g) + 1.88N2(g) → H2O(g) + 1.88N2(g)",
422
+ "reactants": {"H2(g)": 1, "O2(g)": 0.5, "N2(g)": 1.88},
423
+ "products": {"H2O(g)": 1, "N2(g)": 1.88}
424
+ },
425
+ {
426
+ "name": "Combustion of Carbon Monoxide",
427
+ "fuel": "Carbon Monoxide",
428
+ "equation": "CO(g) + 0.5O2(g) + 1.88N2(g) → CO2(g) + 1.88N2(g)",
429
+ "reactants": {"CO(g)": 1, "O2(g)": 0.5, "N2(g)": 1.88},
430
+ "products": {"CO2(g)": 1, "N2(g)": 1.88}
431
+ },
432
+ {
433
+ "name": "Combustion of Acetylene",
434
+ "fuel": "Acetylene",
435
+ "equation": "C2H2(g) + 2.5O2(g) + 9.4N2(g) → 2CO2(g) + H2O(g) + 9.4N2(g)",
436
+ "reactants": {"C2H2(g)": 1, "O2(g)": 2.5, "N2(g)": 9.4},
437
+ "products": {"CO2(g)": 2, "H2O(g)": 1, "N2(g)": 9.4}
438
+ },
439
+ {
440
+ "name": "Combustion of Ethane",
441
+ "fuel": "Ethane",
442
+ "equation": "C2H6(g) + 3.5O2(g) + 13.16N2(g) → 2CO2(g) + 3H2O(g) + 13.16N2(g)",
443
+ "reactants": {"C2H6(g)": 1, "O2(g)": 3.5, "N2(g)": 13.16},
444
+ "products": {"CO2(g)": 2, "H2O(g)": 3, "N2(g)": 13.16}
445
+ },
446
+ {
447
+ "name": "Combustion of Butane",
448
+ "fuel": "Butane",
449
+ "equation": "C4H10(g) + 6.5O2(g) + 24.44N2(g) → 4CO2(g) + 5H2O(g) + 24.44N2(g)",
450
+ "reactants": {"C4H10(g)": 1, "O2(g)": 6.5, "N2(g)": 24.44},
451
+ "products": {"CO2(g)": 4, "H2O(g)": 5, "N2(g)": 24.44}
452
+ },
453
+ {
454
+ "name": "Combustion of Octane",
455
+ "fuel": "Octane",
456
+ "equation": "C8H18(g) + 12.5O2(g) + 47.0N2(g) → 8CO2(g) + 9H2O(g) + 47.0N2(g)",
457
+ "reactants": {"C8H18(g)": 1, "O2(g)": 12.5, "N2(g)": 47.0},
458
+ "products": {"CO2(g)": 8, "H2O(g)": 9, "N2(g)": 47.0}
459
+ },
460
+ {
461
+ "name": "Combustion of Ammonia",
462
+ "fuel": "Ammonia",
463
+ "equation": "4NH3(g) + 3O2(g) + 11.28N2(g) → 2N2(g) + 6H2O(g) + 11.28N2(g)",
464
+ "reactants": {"NH3(g)": 4, "O2(g)": 3, "N2(g)": 11.28},
465
+ "products": {"N2(g)": 13.28, "H2O(g)": 6} # Total N2 = 2 + 11.28
466
+ },
467
+ {
468
+ "name": "Combustion of Methanol",
469
+ "fuel": "Methanol",
470
+ "equation": "CH3OH(g) + 1.5O2(g) + 5.64N2(g) → CO2(g) + 2H2O(g) + 5.64N2(g)",
471
+ "reactants": {"CH3OH(g)": 1, "O2(g)": 1.5, "N2(g)": 5.64},
472
+ "products": {"CO2(g)": 1, "H2O(g)": 2, "N2(g)": 5.64}
473
+ },
474
+ {
475
+ "name": "Combustion of Ethanol",
476
+ "fuel": "Ethanol",
477
+ "equation": "C2H5OH(g) + 3O2(g) + 11.28N2(g) → 2CO2(g) + 3H2O(g) + 11.28N2(g)",
478
+ "reactants": {"C2H5OH(g)": 1, "O2(g)": 3, "N2(g)": 11.28},
479
+ "products": {"CO2(g)": 2, "H2O(g)": 3, "N2(g)": 11.28}
480
+ }
481
+ ]
482
+
483
+
484
+ # Properties are given at 20°C (293.15 K) and standard pressure (101.325 kPa) unless otherwise noted.
485
+ # Viscosity can vary between sources. Values chosen for typical textbook accuracy.
486
+ # Format: { "Name": (Density [kg/m³], Dynamic Viscosity [Pa·s]) }
487
+ COMMON_LIQUIDS = {
488
+ # Water and Common Solvents
489
+ "Water": (998.2, 1.002e-3),
490
+ "Seawater (3.5% salinity)": (1025, 1.07e-3), # Viscosity approx. 7% higher than pure water
491
+ "Ethanol": (789.4, 1.074e-3),
492
+ "Methanol": (791.3, 0.544e-3),
493
+ "Isopropyl Alcohol (IPA)": (781.8, 2.04e-3),
494
+ "Acetone": (784.5, 0.306e-3),
495
+ "Benzene": (876.5, 0.601e-3),
496
+ "Toluene": (866.9, 0.560e-3),
497
+ "Diethyl Ether": (713.4, 0.223e-3),
498
+ "n-Hexane": (654.8, 0.294e-3),
499
+
500
+ # Oils and Hydrocarbons
501
+ "Engine Oil (SAE 10W)": (870, 0.065),
502
+ "Engine Oil (SAE 30)": (891.0, 0.290),
503
+ "Engine Oil (SAE 50)": (902, 0.860),
504
+ "Gear Oil (SAE 90)": (915, 0.700),
505
+ "Crude Oil (light)": (850, 7.5e-3),
506
+ "Kerosene": (810, 1.6e-3),
507
+ "Diesel Fuel": (850, 3.5e-3),
508
+ "Gasoline": (745, 0.29e-3), # ~0.4-0.8 cP, often approximated to water's order of magnitude
509
+
510
+ # Organic & Food Grade Liquids
511
+ "Glycerol (100%)": (1261.3, 1.490),
512
+ "Olive Oil": (910, 0.081),
513
+ "Corn Syrup": (1380, 5.0), # Highly variable with concentration and temp
514
+ "Honey": (1420, 10.0), # Highly variable with type and temp
515
+ "Milk (whole)": (1035, 2.0e-3),
516
+ "Blood Plasma (human)": (1025, 1.5e-3),
517
+ "Blood (whole, human)": (1060, 4.0e-3), # Shear-thinning, this is an approx. value
518
+
519
+ # Cryogens & Liquefied Gases (at their boiling point @ 1 atm)
520
+ "Liquid Nitrogen (77 K)": (804, 0.158e-3), # Note: Temperature is 77 K (-196°C)
521
+ "Liquid Oxygen (90 K)": (1141, 0.189e-3), # Temperature is 90 K (-183°C)
522
+
523
+ # Metals and Inorganics
524
+ "Mercury": (13593, 1.526e-3),
525
+ "Sulfuric Acid (98%)": (1831, 25.4e-3),
526
+ "Ethylene Glycol": (1113.4, 16.1e-3), # Common antifreeze
527
+ }
528
+
529
+
530
+ # Properties are given at 20°C (293.15 K) and 1 atm (101.325 kPa) unless otherwise noted.
531
+ # Format: { "Name": (Density [kg/m³], Dynamic Viscosity [Pa·s]) }
532
+ COMMON_GASES = {
533
+ # Common Gases
534
+ "Air": (1.204, 1.81e-5),
535
+ "Nitrogen (N₂)": (1.165, 1.76e-5),
536
+ "Oxygen (O₂)": (1.331, 2.00e-5),
537
+ "Carbon Dioxide (CO₂)": (1.842, 1.47e-5), # Viscosity is temperature-dependent and increases for CO2
538
+ "Argon": (1.661, 2.23e-5),
539
+ "Helium": (0.166, 1.96e-5),
540
+ "Neon": (0.840, 3.18e-5),
541
+ "Krypton": (3.479, 2.55e-5),
542
+ "Xenon": (5.495, 2.28e-5), # Density high, but viscosity is similar to air
543
+
544
+ # Hydrocarbons
545
+ "Methane (CH₄)": (0.668, 1.09e-5),
546
+ "Ethane (C₂H₆)": (1.264, 9.15e-6),
547
+ "Propane (C₃H₈)": (1.880, 8.00e-6), # Note: Viscosity decreases slightly with molecular weight in this series
548
+ "Butane (C₄H₁₀)": (2.489, 7.50e-6),
549
+ "Natural Gas (approx.)": (0.700, 1.10e-5), # Modeled after methane
550
+ "Acetylene (C₂H₂)": (1.092, 9.80e-6),
551
+
552
+ # Other Common Gases
553
+ "Hydrogen (H₂)": (0.0838, 8.90e-6), # Lowest density, very low viscosity
554
+ "Steam (Water Vapor)": (0.747, 1.02e-5), # At 100°C (373 K), 1 atm
555
+ "Ammonia (NH₃)": (0.718, 1.01e-5),
556
+ "Chlorine (Cl₂)": (2.994, 1.33e-5),
557
+ "Sulfur Hexafluoride (SF₆)": (6.17, 1.59e-5), # High-density gas used in industry
558
+
559
+ # Noble Gases
560
+ "Radon": (9.23, 2.30e-5), # Theoretical value at 20°C; highly radioactive
561
+ }
562
+
563
+
564
+ # Molecular parameters for the Chapman-Enskog equation (Kinetic Theory)
565
+ # Sources: NIST, CRC Handbook, and standard chemical engineering texts.
566
+ # Format: { "Name": (Molar Mass [g/mol], Sigma σ [Å], Epsilon ε / k [K]) }
567
+ # Note: Epsilon ε / k (the Lennard-Jones energy parameter) is included for calculating the collision integral.
568
+ GAS_MOLECULAR_PARAMS = {
569
+ "Air": (28.97, 3.62, 97.0),
570
+ "Nitrogen (N₂)": (28.01, 3.70, 95.05),
571
+ "Oxygen (O₂)": (32.00, 3.46, 106.7),
572
+ "Carbon Dioxide (CO₂)": (44.01, 3.94, 195.2),
573
+ "Argon": (39.95, 3.54, 93.3),
574
+ "Helium": (4.003, 2.55, 10.22),
575
+ "Neon": (20.18, 2.92, 32.8),
576
+ "Krypton": (83.80, 3.69, 178.9),
577
+ "Xenon": (131.29, 4.10, 231.0),
578
+ "Methane (CH₄)": (16.04, 3.78, 148.6),
579
+ "Ethane (C₂H₆)": (30.07, 4.42, 215.7),
580
+ "Propane (C₃H₈)": (44.10, 5.06, 237.1),
581
+ "Butane (C₄H₁₀)": (58.12, 5.47, 531.4), # n-butane
582
+ "Hydrogen (H₂)": (2.016, 2.93, 33.3),
583
+ "Ammonia (NH₃)": (17.03, 2.92, 558.3), # Polar molecule, value is an effective fit.
584
+ "Chlorine (Cl₂)": (70.90, 4.40, 316.0),
585
+ "Sulfur Hexafluoride (SF₆)": (146.06, 5.51, 222.1),
586
+ }
587
+
588
+
589
+ # Properties for non-Newtonian power-law fluids
590
+ # Format: { "Name": (Consistency Index K [Pa·s^n], Power-Law Index n [dimensionless]) }
591
+ POWER_LAW_FLUIDS = {
592
+ # Common Household & Food (Shear-Thinning)
593
+ "Ketchup": (32.5, 0.22),
594
+ "Applesauce": (15.0, 0.3),
595
+ "Mustard": (50.0, 0.28),
596
+ "Mayonnaise": (85.0, 0.6),
597
+ "Tomato Puree": (25.0, 0.5),
598
+ "Yogurt": (12.5, 0.6),
599
+ "Toothpaste": (120.0, 0.4),
600
+ "Shampoo": (25.0, 0.6),
601
+ "Hand Lotion": (80.0, 0.5),
602
+
603
+ # Paints & Inks (Shear-Thinning)
604
+ "Latex Paint": (45.0, 0.45),
605
+ "Printing Ink": (10.0, 0.7),
606
+
607
+ # Biological Fluids (Mostly Shear-Thinning)
608
+ "Blood (Plasma)": (0.012, 0.95), # Very low K, nearly Newtonian
609
+ "Mucus": (10.0, 0.5),
610
+
611
+ # Polymer Solutions & Melts (Shear-Thinning)
612
+ "0.5% Carboxymethylcellulose (CMC) in Water": (1.5, 0.6),
613
+ "1.5% Polyacrylamide in Water": (5.0, 0.3),
614
+ "Molten Polyethylene": (5000.0, 0.6), # K is very temperature-dependent
615
+
616
+ # Newtonian Baseline (n = 1)
617
+ "Water": (0.001, 1.0), # K is the dynamic viscosity
618
+ "Glycerol": (1.0, 1.0),
619
+ "Air": (1.8e-5, 1.0), # K is the dynamic viscosity
620
+
621
+ # Shear-Thickening (Dilatant)
622
+ "Corn Starch Suspension (40%)": (2.0, 1.9),
623
+ "Corn Starch Suspension (50%)": (10.0, 2.5), # Higher concentration -> stronger effect
624
+ "Silica Sand Suspension (60%)": (0.5, 1.6),
625
+ }
626
+
627
+
628
+ # Standard gravitational acceleration in m/s²
629
+ GRAVITATIONAL_ACCELERATION = 9.81
data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/conversion_and_reactor_sizing.cpython-311.pyc ADDED
Binary file (11.9 kB). View file
 
data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/mole_balances.cpython-311.pyc ADDED
Binary file (19.3 kB). View file
 
data/templates/branches/chemical_engineering/reaction_kinetics/conversion_and_reactor_sizing.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import numpy as np
3
+ from data.templates.branches.chemical_engineering.constants import GENERAL_REACTANTS
4
+
5
+
6
+ # Template 1 (Advanced)
7
+ def template_levenspiel_plot_interpretation():
8
+ """
9
+ Levenspiel Plot Data Interpretation for Reactor Volume Calculation
10
+
11
+ Scenario:
12
+ Experimental data of 1/(-r_A) vs. conversion (X) is provided in tabulated form.
13
+ The goal is to determine the reactor volumes required to achieve a target
14
+ conversion without explicitly knowing the rate law. Two cases are considered:
15
+
16
+ - Continuous Stirred-Tank Reactor (CSTR):
17
+ Volume is calculated using the rectangular approximation:
18
+ V_CSTR = F_A0 * X * [1/(-r_A)]_exit
19
+
20
+ - Plug Flow Reactor (PFR):
21
+ Volume is calculated using trapezoidal integration of the curve:
22
+ V_PFR = F_A0 * ∫₀ˣ (1/(-r_A)) dX
23
+
24
+ Returns:
25
+ tuple: A tuple containing:
26
+ - str: A question asking to compute both CSTR and PFR volumes from tabulated data.
27
+ - str: A step-by-step solution showing the calculations, including trapezoidal rule details.
28
+ """
29
+
30
+ # 1. Generate variable parameters with validation
31
+ reactant_name = random.choice(GENERAL_REACTANTS)
32
+ F_A0 = round(random.uniform(1.0, 5.0), 2) # mol/s
33
+
34
+ # Ensure F_A0 is positive
35
+ if F_A0 <= 0:
36
+ F_A0 = 2.0 # Default fallback
37
+
38
+ # Variable target conversion instead of fixed 0.8
39
+ target_conversion_options = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9]
40
+ target_conversion = random.choice(target_conversion_options)
41
+
42
+ # 2. Generate conversion points dynamically based on target conversion
43
+ if target_conversion <= 0.6:
44
+ num_points = 7
45
+ elif target_conversion <= 0.8:
46
+ num_points = 9
47
+ else:
48
+ num_points = 10 # More points for higher conversions
49
+
50
+ X_values = np.linspace(0.0, target_conversion, num_points)
51
+ X_values = np.round(X_values, 2) # Clean up floating point artifacts
52
+
53
+ # 3. Generate realistic 1/(-r_A) values with proper validation
54
+ # Base pattern that increases with conversion (typical behavior)
55
+ base_pattern = 1.5 + 2.0 * X_values + 3.0 * X_values**2
56
+
57
+ # Add controlled randomness
58
+ random_factor = random.uniform(0.8, 1.4)
59
+ noise = 1 + 0.15 * np.random.uniform(-1, 1, num_points)
60
+
61
+ # Ensure noise doesn't make values negative or too small
62
+ noise = np.maximum(noise, 0.5)
63
+
64
+ inv_rate_values = base_pattern * random_factor * noise
65
+ inv_rate_values = np.round(inv_rate_values, 2)
66
+
67
+ # 4. Data validation and correction
68
+ # Ensure first value is reasonable (at X=0)
69
+ if inv_rate_values[0] < 1.0:
70
+ inv_rate_values[0] = round(random.uniform(1.0, 2.5), 2)
71
+
72
+ # Ensure all values are positive
73
+ inv_rate_values = np.maximum(inv_rate_values, 0.5)
74
+
75
+ # Enforce general increasing trend (physical expectation)
76
+ for i in range(1, len(inv_rate_values)):
77
+ if inv_rate_values[i] < inv_rate_values[i-1]:
78
+ inv_rate_values[i] = inv_rate_values[i-1] + round(random.uniform(0.1, 0.3), 2)
79
+
80
+ # Final validation - ensure no extremely large values
81
+ inv_rate_values = np.minimum(inv_rate_values, 50.0)
82
+
83
+ # 5. Calculate CSTR volume with error checking
84
+ try:
85
+ inv_rate_at_final = inv_rate_values[-1] # 1/(-r_A) at final X
86
+ X_final = X_values[-1]
87
+
88
+ if inv_rate_at_final <= 0:
89
+ inv_rate_at_final = 5.0 # Fallback value
90
+
91
+ V_CSTR = F_A0 * inv_rate_at_final * X_final
92
+
93
+ # Ensure positive volume
94
+ if V_CSTR <= 0:
95
+ V_CSTR = F_A0 * 5.0 * X_final # Fallback calculation
96
+
97
+ except Exception:
98
+ # Fallback CSTR calculation
99
+ V_CSTR = F_A0 * 8.0 * target_conversion
100
+
101
+ # 6. Calculate PFR volume with error checking
102
+ try:
103
+ # Validate data before integration
104
+ if len(X_values) != len(inv_rate_values):
105
+ raise ValueError("Data arrays have mismatched lengths")
106
+
107
+ if np.any(inv_rate_values <= 0):
108
+ raise ValueError("Negative or zero rate values detected")
109
+
110
+ # Check for monotonic X values
111
+ if not np.all(np.diff(X_values) >= 0):
112
+ raise ValueError("X values not monotonically increasing")
113
+
114
+ area_under_curve = np.trapezoid(inv_rate_values, X_values)
115
+
116
+ if area_under_curve <= 0:
117
+ raise ValueError("Negative area under curve")
118
+
119
+ V_PFR = F_A0 * area_under_curve
120
+
121
+ # Sanity check on PFR volume
122
+ if V_PFR <= 0 or V_PFR > 10 * V_CSTR:
123
+ raise ValueError("PFR volume unrealistic")
124
+
125
+ except Exception:
126
+ # Fallback PFR calculation using simple approximation
127
+ avg_inv_rate = np.mean(inv_rate_values) if len(inv_rate_values) > 0 else 5.0
128
+ V_PFR = F_A0 * avg_inv_rate * target_conversion * 0.7 # Approximate PFR advantage
129
+
130
+ # 7. Final physical validation
131
+ if V_CSTR <= 0:
132
+ V_CSTR = abs(V_CSTR) + 1.0
133
+ if V_PFR <= 0:
134
+ V_PFR = abs(V_PFR) + 1.0
135
+
136
+ # Ensure PFR is typically more efficient (but allow exceptions)
137
+ if V_PFR > 1.5 * V_CSTR:
138
+ # Unusual case - might indicate data issues, but proceed with warning
139
+ unusual_case = True
140
+ else:
141
+ unusual_case = False
142
+
143
+ # 8. Create data table for display
144
+ data_table = "X\t1/(-r_A) [L·s/mol]\n"
145
+ data_table += "-" * 25 + "\n"
146
+ for x, inv_r in zip(X_values, inv_rate_values):
147
+ data_table += f"{x:.2f}\t{inv_r:.2f}\n"
148
+
149
+ # 9. Generate question
150
+ question = (
151
+ f"A reaction involving {reactant_name} (A → products) is being studied for reactor design. "
152
+ f"Experimental data has been collected and plotted as a Levenspiel plot (1/(-r_A) vs X). "
153
+ f"The inlet molar flow rate is {F_A0} mol/s. Using the data below, calculate:\n\n"
154
+ f"a) The volume of a CSTR required to achieve {target_conversion*100:.0f}% conversion\n"
155
+ f"b) The volume of a PFR required to achieve {target_conversion*100:.0f}% conversion\n\n"
156
+ f"Levenspiel Plot Data:\n"
157
+ f"{data_table}"
158
+ )
159
+
160
+ # 10. Generate solution
161
+ solution = (
162
+ f"**Given:**\n"
163
+ f"- Reactant: {reactant_name}\n"
164
+ f"- Inlet molar flow rate: F_A0 = {F_A0} mol/s\n"
165
+ f"- Target conversion: X = {target_conversion} ({target_conversion*100:.0f}%)\n"
166
+ f"- Data points: {len(X_values)} experimental values\n\n"
167
+
168
+ f"**Part (a): CSTR Volume Calculation**\n\n"
169
+ f"**Step 1:** For a CSTR, the design equation is:\n"
170
+ f"V_CSTR = F_A0 × X × [1/(-r_A)]_exit\n\n"
171
+
172
+ f"**Step 2:** From the data table, at X = {target_conversion}:\n"
173
+ f"[1/(-r_A)]_at_X={target_conversion} = {inv_rate_at_final:.2f} L·s/mol\n\n"
174
+
175
+ f"**Step 3:** Calculate CSTR volume:\n"
176
+ f"V_CSTR = {F_A0} mol/s × {target_conversion} × {inv_rate_at_final:.2f} L·s/mol\n"
177
+ f"V_CSTR = {round(V_CSTR, 2)} L\n\n"
178
+
179
+ f"**Part (b): PFR Volume Calculation**\n\n"
180
+ f"**Step 1:** For a PFR, the design equation is:\n"
181
+ f"V_PFR = F_A0 × ∫[0 to X] (1/(-r_A)) dX\n\n"
182
+
183
+ f"**Step 2:** The integral represents the area under the Levenspiel plot curve.\n"
184
+ f"Using trapezoidal rule for numerical integration:\n\n"
185
+
186
+ f"**Step 3:** Apply trapezoidal rule to the data points:\n"
187
+ f"Area = Σ[(X_i+1 - X_i) × (y_i + y_i+1)/2]\n"
188
+ f"where y_i = [1/(-r_A)]_i\n\n"
189
+
190
+ f"**Step 4:** Calculate individual trapezoid areas:\n"
191
+ )
192
+
193
+ # Add detailed trapezoidal calculation with error handling
194
+ total_area = 0
195
+ try:
196
+ for i in range(len(X_values)-1):
197
+ dx = X_values[i+1] - X_values[i]
198
+ avg_height = (inv_rate_values[i] + inv_rate_values[i+1]) / 2
199
+ trap_area = dx * avg_height
200
+
201
+ # Validate each trapezoid calculation
202
+ if dx <= 0 or avg_height <= 0:
203
+ continue # Skip invalid intervals
204
+
205
+ total_area += trap_area
206
+
207
+ solution += (
208
+ f"Interval [{X_values[i]:.2f} to {X_values[i+1]:.2f}]: "
209
+ f"ΔX = {dx:.2f}, Avg height = ({inv_rate_values[i]:.2f} + {inv_rate_values[i+1]:.2f})/2 = {avg_height:.2f}\n"
210
+ f"Area = {dx:.2f} × {avg_height:.2f} = {trap_area:.3f}\n"
211
+ )
212
+ except Exception:
213
+ # Fallback area calculation
214
+ total_area = area_under_curve if 'area_under_curve' in locals() else np.mean(inv_rate_values) * target_conversion
215
+
216
+ solution += (
217
+ f"\n**Step 5:** Total area under curve:\n"
218
+ f"Total area = {total_area:.3f}\n\n"
219
+
220
+ f"**Step 6:** Calculate PFR volume:\n"
221
+ f"V_PFR = F_A0 × Area = {F_A0} mol/s × {total_area:.3f}\n"
222
+ f"V_PFR = {round(V_PFR, 2)} L\n\n"
223
+
224
+ f"**Final Answers:**\n"
225
+ f"a) CSTR Volume = {round(V_CSTR, 2)} L\n"
226
+ f"b) PFR Volume = {round(V_PFR, 2)} L\n\n"
227
+ )
228
+
229
+ # Add appropriate comparison note based on results
230
+ if unusual_case:
231
+ comparison_note = (
232
+ f"**Note:** In this case, the PFR volume ({round(V_PFR, 2)} L) is unusually large compared to "
233
+ f"the CSTR volume ({round(V_CSTR, 2)} L). This could indicate very flat kinetics or "
234
+ f"experimental measurement uncertainty."
235
+ )
236
+ elif V_PFR < V_CSTR:
237
+ efficiency = ((V_CSTR - V_PFR) / V_CSTR) * 100
238
+ comparison_note = (
239
+ f"**Note:** The PFR requires less volume than the CSTR ({round(V_PFR, 2)} L vs {round(V_CSTR, 2)} L), "
240
+ f"providing a {efficiency:.1f}% volume reduction due to more efficient use of reaction kinetics."
241
+ )
242
+ else:
243
+ comparison_note = (
244
+ f"**Note:** The reactor volumes are similar ({round(V_CSTR, 2)} L vs {round(V_PFR, 2)} L), "
245
+ f"suggesting relatively flat kinetics over this conversion range."
246
+ )
247
+
248
+ solution += comparison_note
249
+
250
+ return question, solution
251
+
252
+
253
+ def main():
254
+ """
255
+ Generate numerous instances of the Levenspiel plot template with different random seeds
256
+ and write the results to a JSONL file.
257
+ """
258
+
259
+ import json
260
+ import os
261
+
262
+ # Define the output path (Modify this path according to where you are running the code from)
263
+ output_file = "testset/chemical_engineering/reaction_kinetics/conversion_and_reactor_sizing.jsonl"
264
+
265
+ # Create the directory if it doesn't exist
266
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
267
+
268
+ # List of template functions with their ID and level
269
+ templates = [
270
+ (template_levenspiel_plot_interpretation, "levenspiel_plot_interpretation", "Advanced")
271
+ ]
272
+
273
+ # List to store all generated problems
274
+ all_problems = []
275
+
276
+ # Generate problems for each template in the list
277
+ for template_func, id_name, level in templates:
278
+ for _ in range(50):
279
+ # Generate a unique seed for reproducibility
280
+ seed = random.randint(1_000_000_000, 4_000_000_000)
281
+ random.seed(seed)
282
+
283
+ # Generate the question and solution by calling the function
284
+ question, solution = template_func()
285
+
286
+ # Create a dictionary entry for the problem
287
+ problem_entry = {
288
+ "seed": seed,
289
+ "branch": "chemical_engineering",
290
+ "domain": "reaction_kinetics",
291
+ "area": "conversion_and_reactor_sizing",
292
+ "id": id_name,
293
+ "level": level,
294
+ "question": question,
295
+ "solution": solution
296
+ }
297
+
298
+ # Add the new problem to our list
299
+ all_problems.append(problem_entry)
300
+
301
+ # Write all generated problems to a .jsonl file (JSON Lines format)
302
+ with open(output_file, "w") as file:
303
+ for problem in all_problems:
304
+ file.write(json.dumps(problem))
305
+ file.write("\n")
306
+
307
+ print(f"\nSuccess! Generated {len(all_problems)} problems and saved them to '{output_file}'")
308
+
309
+
310
+ if __name__ == "__main__":
311
+ main()
data/templates/branches/chemical_engineering/reaction_kinetics/mole_balances.py ADDED
@@ -0,0 +1,431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ from scipy.integrate import quad
4
+ from data.templates.branches.chemical_engineering.constants import LIQUID_PHASE_REACTANTS, GENERAL_REACTANTS
5
+
6
+
7
+ # Template 1 (Easy)
8
+ def template_cstr_volume_basic():
9
+ """
10
+ CSTR Volume Calculation
11
+
12
+ Scenario:
13
+ The General Mole Balance for a Continuous Stirred-Tank Reactor (CSTR) at steady
14
+ state is used to determine the necessary reactor volume to achieve a desired
15
+ outcome. Given the inlet and outlet molar flow rates and the reaction rate,
16
+ the goal is to compute the volume using:
17
+
18
+ V = (F_A0 - F_A) / (-r_A)
19
+
20
+ Returns:
21
+ tuple: A tuple containing:
22
+ - str: A question asking to compute the CSTR volume.
23
+ - str: A step-by-step solution showing the calculation (the Python trace).
24
+ """
25
+ # 1. Parameterize the inputs with random values
26
+ reactant_name = random.choice(GENERAL_REACTANTS)
27
+ # Inlet molar flow rate in mol/s
28
+ F_A0 = round(random.uniform(1.0, 5.0), 2)
29
+ # Outlet molar flow rate in mol/s (must be less than inlet)
30
+ F_A = round(random.uniform(0.1, F_A0 - 0.1), 2)
31
+ # Reaction rate in mol/(L·s) - a positive value for -r_A
32
+ neg_r_A = round(random.uniform(0.01, 0.05), 4)
33
+
34
+ # 2. Perform the core calculation
35
+ volume = (F_A0 - F_A) / neg_r_A
36
+
37
+ # 3. Generate the question and solution strings
38
+ question = (
39
+ f"A steady-state CSTR is used for the consumption of {reactant_name}. "
40
+ f"The inlet molar flow rate of {reactant_name} is {F_A0} mol/s, and the desired "
41
+ f"outlet molar flow rate is {F_A} mol/s. If the rate of reaction is {neg_r_A} mol/(L·s), "
42
+ f"what is the required reactor volume in liters?"
43
+ )
44
+
45
+ solution = (
46
+ f"**Step 1:** State the CSTR design equation.\n"
47
+ f" V = (F_A0 - F_A) / (-r_A)\n\n"
48
+ f"**Step 2:** Substitute the given values into the equation.\n"
49
+ f" V = ({F_A0} mol/s - {F_A} mol/s) / ({neg_r_A} mol/(L·s))\n\n"
50
+ f"**Step 3:** Calculate the final volume.\n"
51
+ f" V = {round(volume, 2)} L\n\n"
52
+ f"**Answer:** The required reactor volume is {round(volume, 2)} liters."
53
+ )
54
+
55
+ return question, solution
56
+
57
+
58
+ # Template 2 (Easy)
59
+ def template_batch_reactor_zero_order():
60
+ """
61
+ Batch Reactor Time Calculation - Zero Order Kinetics
62
+
63
+ Scenario:
64
+ The time required for a reaction in a constant-volume batch reactor is
65
+ determined by integrating the mole balance equation. For a zero-order
66
+ reaction, the rate of reaction is constant and independent of concentration.
67
+ Given the initial and final concentrations and the rate constant, the
68
+ goal is to compute the necessary time using the integrated rate law:
69
+
70
+ t = (C_A0 - C_A) / k
71
+
72
+ Returns:
73
+ tuple: A tuple containing:
74
+ - str: A question asking to calculate the required reaction time.
75
+ - str: A step-by-step solution showing the derivation and calculation.
76
+ """
77
+
78
+ # 1. Parameterize inputs
79
+ reactant_name = random.choice(LIQUID_PHASE_REACTANTS)
80
+ C_A0 = round(random.uniform(2.0, 10.0), 2) # Initial concentration (mol/L)
81
+
82
+ # Generate final concentration ensuring reasonable conversion
83
+ conversion = round(random.uniform(0.2, 0.8), 2)
84
+ C_A = round(C_A0 * (1 - conversion), 2)
85
+
86
+ # Zero-order rate constant (mol/(L·s))
87
+ k = round(random.uniform(0.01, 0.1), 4)
88
+
89
+ # Calculate time
90
+ time = (C_A0 - C_A) / k
91
+
92
+ # Generate question and solution
93
+ question = (
94
+ f"A zero-order liquid-phase reaction involving {reactant_name} is carried out "
95
+ f"in a constant-volume batch reactor. The initial concentration is {C_A0} mol/L, "
96
+ f"and the desired final concentration is {C_A} mol/L. If the zero-order rate "
97
+ f"constant is {k} mol/(L·s), calculate the required reaction time."
98
+ )
99
+
100
+ solution = (
101
+ f"**Given:**\n"
102
+ f"- Initial concentration (C_A0) = {C_A0} mol/L\n"
103
+ f"- Final concentration (C_A) = {C_A} mol/L\n"
104
+ f"- Zero-order rate constant (k) = {k} mol/(L·s)\n"
105
+ f"- Conversion = {round(conversion * 100, 1)}%\n\n"
106
+
107
+ f"**Step 1:** Write the mole balance for a constant-volume batch reactor.\n"
108
+ f"dC_A/dt = r_A\n\n"
109
+
110
+ f"**Step 2:** For zero-order kinetics, r_A = -k (constant).\n"
111
+ f"dC_A/dt = -k\n\n"
112
+
113
+ f"**Step 3:** Integrate from t=0 (C_A = C_A0) to t=t (C_A = C_A).\n"
114
+ f"∫[C_A0 to C_A] dC_A = -k ∫[0 to t] dt\n"
115
+ f"C_A - C_A0 = -k × t\n\n"
116
+
117
+ f"**Step 4:** Solve for time.\n"
118
+ f"t = (C_A0 - C_A) / k\n\n"
119
+
120
+ f"**Step 5:** Substitute values.\n"
121
+ f"t = ({C_A0} - {C_A}) mol/L / {k} mol/(L·s)\n"
122
+ f"t = {C_A0 - C_A} / {k} = {round(time, 2)} s\n\n"
123
+
124
+ f"**Answer:** The required reaction time is {round(time, 2)} seconds."
125
+ )
126
+
127
+ return question, solution
128
+
129
+
130
+ # Template 3 (Intermediate)
131
+ def template_batch_reactor_first_order():
132
+ """
133
+ Batch Reactor Time Calculation - First Order Kinetics
134
+
135
+ Scenario:
136
+ This template calculates the time for a reaction in a constant-volume
137
+ batch reactor following first-order kinetics. Unlike zero-order reactions,
138
+ the rate of a first-order reaction is directly proportional to the
139
+ concentration of the reactant (-r_A = k*C_A). The required time is found
140
+ by integrating the mole balance equation. Given the initial and final
141
+ concentrations and the rate constant, the goal is to compute the time using:
142
+
143
+ t = (1/k) * ln(C_A0 / C_A)
144
+
145
+ Returns:
146
+ tuple: A tuple containing:
147
+ - str: A question asking to calculate the required reaction time.
148
+ - str: A step-by-step solution showing the derivation and calculation.
149
+ """
150
+
151
+ reactant_name = random.choice(LIQUID_PHASE_REACTANTS)
152
+ C_A0 = round(random.uniform(1.0, 5.0), 2)
153
+
154
+ conversion = round(random.uniform(0.3, 0.9), 2)
155
+ C_A = round(C_A0 * (1 - conversion), 2)
156
+
157
+ # First-order rate constant (1/s)
158
+ k = round(random.uniform(0.001, 0.01), 5)
159
+
160
+ # Calculate time
161
+ time = math.log(C_A0 / C_A) / k
162
+
163
+ question = (
164
+ f"A first-order liquid-phase reaction of {reactant_name} occurs in a batch reactor. "
165
+ f"The initial concentration is {C_A0} mol/L, and after reaction, the concentration "
166
+ f"decreases to {C_A} mol/L. If the first-order rate constant is {k} s⁻¹, "
167
+ f"determine the reaction time required."
168
+ )
169
+
170
+ solution = (
171
+ f"**Given:**\n"
172
+ f"- Initial concentration (C_A0) = {C_A0} mol/L\n"
173
+ f"- Final concentration (C_A) = {C_A} mol/L\n"
174
+ f"- First-order rate constant (k) = {k} s⁻¹\n"
175
+ f"- Conversion = {round(conversion * 100, 1)}%\n\n"
176
+
177
+ f"**Step 1:** Write the rate law for first-order kinetics.\n"
178
+ f"-r_A = k × C_A\n\n"
179
+
180
+ f"**Step 2:** Set up the mole balance equation.\n"
181
+ f"dC_A/dt = -k × C_A\n\n"
182
+
183
+ f"**Step 3:** Separate variables and integrate.\n"
184
+ f"dC_A/C_A = -k × dt\n"
185
+ f"∫[C_A0 to C_A] dC_A/C_A = -k ∫[0 to t] dt\n\n"
186
+
187
+ f"**Step 4:** Solve the integral.\n"
188
+ f"ln(C_A/C_A0) = -k × t\n"
189
+ f"ln(C_A0/C_A) = k × t\n\n"
190
+
191
+ f"**Step 5:** Solve for time.\n"
192
+ f"t = (1/k) × ln(C_A0/C_A)\n\n"
193
+
194
+ f"**Step 6:** Substitute values.\n"
195
+ f"t = (1/{k}) × ln({C_A0}/{C_A})\n"
196
+ f"t = {round(1/k, 2)} × ln({round(C_A0/C_A, 3)})\n"
197
+ f"t = {round(1/k, 2)} × {round(math.log(C_A0/C_A), 3)}\n"
198
+ f"t = {round(time, 2)} s\n\n"
199
+
200
+ f"**Answer:** The required reaction time is {round(time, 2)} seconds."
201
+ )
202
+
203
+ return question, solution
204
+
205
+
206
+ # Template 4 (Advanced)
207
+ def template_batch_reactor_second_order():
208
+ """
209
+ Batch Reactor Time Calculation - Second Order Kinetics
210
+
211
+ Scenario:
212
+ This template calculates the reaction time in a constant-volume batch
213
+ reactor for a second-order reaction. In this case, the reaction rate
214
+ depends on the square of the reactant's concentration (-r_A = k*C_A²).
215
+ The time required is determined by integrating the mole balance
216
+ equation for these kinetics. Given the initial and final concentrations
217
+ and the second-order rate constant, the goal is to compute the time using:
218
+
219
+ t = (1/k) * (1/C_A - 1/C_A0)
220
+
221
+ Returns:
222
+ tuple: A tuple containing:
223
+ - str: A question asking to calculate the required reaction time.
224
+ - str: A step-by-step solution showing the derivation and calculation.
225
+ """
226
+
227
+ reactant_name = random.choice(LIQUID_PHASE_REACTANTS)
228
+ C_A0 = round(random.uniform(0.5, 2.0), 2) # Lower values for second-order
229
+
230
+ conversion = round(random.uniform(0.4, 0.85), 2)
231
+ C_A = round(C_A0 * (1 - conversion), 2)
232
+
233
+ # Second-order rate constant (L/(mol·s))
234
+ k = round(random.uniform(0.1, 1.0), 3)
235
+
236
+ # Calculate time
237
+ time = (1/C_A - 1/C_A0) / k
238
+
239
+ question = (
240
+ f"A second-order reaction of {reactant_name} takes place in a batch reactor. "
241
+ f"Starting with {C_A0} mol/L, the concentration drops to {C_A} mol/L. "
242
+ f"Given that the second-order rate constant is {k} L/(mol·s), "
243
+ f"calculate the time required for this conversion."
244
+ )
245
+
246
+ solution = (
247
+ f"**Given:**\n"
248
+ f"- Initial concentration (C_A0) = {C_A0} mol/L\n"
249
+ f"- Final concentration (C_A) = {C_A} mol/L\n"
250
+ f"- Second-order rate constant (k) = {k} L/(mol·s)\n"
251
+ f"- Conversion = {round(conversion * 100, 1)}%\n\n"
252
+
253
+ f"**Step 1:** Write the rate law for second-order kinetics.\n"
254
+ f"-r_A = k × C_A²\n\n"
255
+
256
+ f"**Step 2:** Set up the mole balance equation.\n"
257
+ f"dC_A/dt = -k × C_A²\n\n"
258
+
259
+ f"**Step 3:** Separate variables and integrate.\n"
260
+ f"dC_A/C_A² = -k × dt\n"
261
+ f"∫[C_A0 to C_A] C_A⁻² dC_A = -k ∫[0 to t] dt\n\n"
262
+
263
+ f"**Step 4:** Solve the integral.\n"
264
+ f"[-1/C_A] from C_A0 to C_A = -k × t\n"
265
+ f"-1/C_A + 1/C_A0 = -k × t\n"
266
+ f"1/C_A - 1/C_A0 = k × t\n\n"
267
+
268
+ f"**Step 5:** Solve for time.\n"
269
+ f"t = (1/k) × (1/C_A - 1/C_A0)\n\n"
270
+
271
+ f"**Step 6:** Substitute values.\n"
272
+ f"t = (1/{k}) × (1/{C_A} - 1/{C_A0})\n"
273
+ f"t = {round(1/k, 3)} × ({round(1/C_A, 3)} - {round(1/C_A0, 3)})\n"
274
+ f"t = {round(1/k, 3)} × {round(1/C_A - 1/C_A0, 3)}\n"
275
+ f"t = {round(time, 2)} s\n\n"
276
+
277
+ f"**Answer:** The required reaction time is {round(time, 2)} seconds."
278
+ )
279
+
280
+ return question, solution
281
+
282
+
283
+ # Template 5 (Advanced)
284
+ def template_pfr_volume_changing_rate():
285
+ """
286
+ PFR Volume Calculation - Arbitrary Order Kinetics
287
+
288
+ Scenario:
289
+ This template calculates the required volume for a Plug Flow Reactor (PFR)
290
+ to achieve a target conversion for a liquid-phase (constant-density)
291
+ reaction. The reaction follows an arbitrary, non-integer order, where the
292
+ rate law is -r_A = k * C_A^n. Because an analytical solution is often
293
+ complex for such cases, the PFR design equation is solved using
294
+ numerical integration:
295
+
296
+ V = ∫[0 to X] (F_A0 / (-r_A)) dX
297
+
298
+ Returns:
299
+ tuple: A tuple containing:
300
+ - str: A question asking to calculate the required reactor volume.
301
+ - str: A step-by-step solution showing the setup and numerical result.
302
+ """
303
+
304
+ reactant_name = random.choice(LIQUID_PHASE_REACTANTS)
305
+ F_A0 = round(random.uniform(1.0, 4.0), 2)
306
+ X_final = round(random.uniform(0.5, 0.85), 2)
307
+
308
+ # Reaction order (non-integer for complexity)
309
+ n = round(random.uniform(1.5, 2.5), 1)
310
+
311
+ # Rate constant and initial concentration
312
+ k = round(random.uniform(0.05, 0.5), 3)
313
+ C_A0 = round(random.uniform(0.5, 2.0), 2) # mol/L
314
+
315
+ def rate_function_advanced(X):
316
+ """Rate as function of conversion: -r_A = k * C_A0^n * (1-X)^n"""
317
+ return k * (C_A0**n) * ((1 - X)**n)
318
+
319
+ def integrand_advanced(X):
320
+ """Integrand for PFR design equation"""
321
+ return F_A0 / rate_function_advanced(X)
322
+
323
+ # Numerical integration (analytical solution complex for arbitrary n)
324
+ volume, integration_error = quad(integrand_advanced, 0, X_final)
325
+
326
+ question = (
327
+ f"An {n}-order liquid-phase reaction of {reactant_name} (A → products) occurs in a PFR. "
328
+ f"The inlet conditions are: F_A0 = {F_A0} mol/s and C_A0 = {C_A0} mol/L. "
329
+ f"The rate expression is: -r_A = {k} × C_A^{n} mol/(L·s). "
330
+ f"Determine the reactor volume needed for {X_final*100}% conversion."
331
+ )
332
+
333
+ solution = (
334
+ f"**Given:**\n"
335
+ f"- Inlet molar flow rate: F_A0 = {F_A0} mol/s\n"
336
+ f"- Initial concentration: C_A0 = {C_A0} mol/L\n"
337
+ f"- Rate constant: k = {k} L^{n-1}/(mol^{n-1}·s)\n"
338
+ f"- Reaction order: n = {n}\n"
339
+ f"- Desired conversion: X = {X_final}\n\n"
340
+
341
+ f"**Step 1:** Express rate in terms of conversion.\n"
342
+ f"C_A = C_A0(1 - X)\n"
343
+ f"-r_A = k × C_A^{n} = k × C_A0^{n} × (1 - X)^{n}\n"
344
+ f"-r_A = {k} × {C_A0}^{n} × (1 - X)^{n}\n"
345
+ f"-r_A = {round(k * (C_A0**n), 4)} × (1 - X)^{n}\n\n"
346
+
347
+ f"**Step 2:** Set up the PFR integral.\n"
348
+ f"V = ∫[0 to {X_final}] (F_A0 / (-r_A)) dX\n"
349
+ f"V = ∫[0 to {X_final}] ({F_A0} / ({round(k * (C_A0**n), 4)} × (1 - X)^{n})) dX\n\n"
350
+
351
+ f"**Step 3:** This integral requires numerical methods for n = {n}.\n"
352
+ f"Using numerical integration (scipy.integrate.quad):\n\n"
353
+
354
+ f"**Step 4:** Numerical result.\n"
355
+ f"V = {round(volume, 2)} L\n"
356
+ f"Integration error: {integration_error:.2e}\n\n"
357
+
358
+ f"**Note:** For non-integer reaction orders, analytical solutions are complex.\n"
359
+ f"Numerical integration is the standard approach in reactor design.\n\n"
360
+
361
+ f"**Answer:** The required PFR volume is {round(volume, 2)} liters."
362
+ )
363
+
364
+ return question, solution
365
+
366
+
367
+ def main():
368
+ """
369
+ Generate numerous instances of each mole balances template with different random seeds
370
+ and write the results to a JSONL file.
371
+ """
372
+ import json
373
+ import os
374
+
375
+ # Define the output path (Modify this path according to where you are running the code from)
376
+ output_file = "testset/chemical_engineering/reaction_kinetics/mole_balances.jsonl"
377
+
378
+ # Create the directory if it doesn't exist
379
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
380
+
381
+ # List of template functions with their ID and level
382
+ templates = [
383
+ (template_cstr_volume_basic, "cstr_volume_basic", "Easy"),
384
+ (template_batch_reactor_zero_order, "batch_reactor_zero_order", "Easy"),
385
+ (template_batch_reactor_first_order, "batch_reactor_first_order", "Intermediate"),
386
+ (template_batch_reactor_second_order, "batch_reactor_second_order", "Advanced"),
387
+ (template_pfr_volume_changing_rate, "pfr_volume_changing_rate", "Advanced"),
388
+ ]
389
+
390
+ # List to store all generated problems
391
+ all_problems = []
392
+
393
+ # Generate problems for each template
394
+ for template_func, id_name, level in templates:
395
+ for _ in range(50):
396
+ # Generate a unique seed for each problem
397
+ seed = random.randint(1_000_000_000, 4_000_000_000)
398
+ random.seed(seed)
399
+
400
+ # Generate the problem and solution
401
+ question, solution = template_func()
402
+
403
+ # Create a JSON entry
404
+ problem_entry = {
405
+ "seed": seed,
406
+ "branch": "chemical_engineering",
407
+ "domain": "reaction_kinetics",
408
+ "area": "mole_balances",
409
+ "id": id_name,
410
+ "level": level,
411
+ "question": question,
412
+ "solution": solution
413
+ }
414
+
415
+ # Add to the list of problems
416
+ all_problems.append(problem_entry)
417
+
418
+ # Shuffle the problems to mix templates and levels
419
+ random.shuffle(all_problems)
420
+
421
+ # Write all problems to a .jsonl file
422
+ with open(output_file, "w") as file:
423
+ for problem in all_problems:
424
+ file.write(json.dumps(problem))
425
+ file.write("\n")
426
+
427
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
428
+
429
+
430
+ if __name__ == "__main__":
431
+ main()
data/templates/branches/chemical_engineering/reaction_kinetics/stoichiometry.py ADDED
@@ -0,0 +1,565 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
6
+ def template_batch_moles_vs_conversion():
7
+ """
8
+ Batch System Moles vs. Conversion
9
+
10
+ Scenario:
11
+ This template tests the fundamental relationship between conversion ($X_A$)
12
+ and the number of moles of each species in a batch reactor. For a given
13
+ reaction like $aA + bB -> cC + dD$, the goal is to calculate the final
14
+ number of moles of all species based on the initial moles and a specific
15
+ conversion of the limiting reactant using the core stoichiometric relations:
16
+
17
+ $N_A = N_{A0}(1 - X_A)$
18
+ $N_B = N_{B0} - (b/a)N_{A0}X_A$
19
+ $N_C = N_{C0} + (c/a)N_{A0}X_A$
20
+
21
+ Returns:
22
+ tuple: A tuple containing:
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)
48
+
49
+ # 2. Core Calculations
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
+
113
+
114
+ # Template 2 (Intermediate)
115
+ def template_flow_system_molar_flow_rates():
116
+ """
117
+ Flow System Molar Flow Rates vs. Conversion
118
+
119
+ Scenario:
120
+ This template tests the ability to determine the outlet molar flow rates
121
+ of all species in a continuous flow reactor (e.g., CSTR, PFR) given the
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
+
153
+ # Generate a realistic conversion
154
+ X_A = round(random.uniform(0.50, 0.90), 2)
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
+
162
+ # Calculate outlet 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
+
238
+
239
+ # Template 3 (Intermediate)
240
+ def template_limiting_reactant():
241
+ """
242
+ Finding and Using the Limiting Reactant
243
+
244
+ Scenario:
245
+ This template adds a crucial preliminary step to stoichiometric calculations.
246
+ Given a reaction and the initial moles of multiple reactants, the user must
247
+ first identify the limiting reactant by comparing the ratio of initial moles
248
+ to the stoichiometric coefficient for each species:
249
+
250
+ Compare: $N_{A0}/a$ vs. $N_{B0}/b$
251
+
252
+ The species with the smaller ratio is limiting. Then, using a given
253
+ conversion with respect to that limiting reactant, the user calculates the
254
+ final moles of all species.
255
+
256
+ Returns:
257
+ tuple: A tuple containing:
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
+
289
+ # Generate a realistic conversion for the limiting reactant
290
+ X = round(random.uniform(0.60, 0.95), 2)
291
+
292
+ # 2. Core Calculations & Logic
293
+
294
+ # Determine the limiting reactant
295
+ ratio_A = N_A0 / a
296
+ ratio_B = N_B0 / b
297
+
298
+ if ratio_A <= ratio_B:
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)
306
+ N_B = N_B0 - (b / a) * N_A0 * X
307
+ N_C = N_C0 + (c / a) * N_A0 * X
308
+ N_D = N_D0 + (d / a) * N_A0 * X
309
+ else:
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
317
+ N_B = N_B0 * (1 - X)
318
+ N_C = N_C0 + (c / b) * N_B0 * X
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
+
339
+ solution_step1_identification = (
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
+
395
+
396
+ # Template 4 (Advanced)
397
+ def template_gas_phase_concentration():
398
+ """
399
+ Gas-Phase Concentration with Volumetric Change
400
+
401
+ Scenario:
402
+ For gas-phase reactions with a change in the total number of moles, the
403
+ volumetric flow rate varies with conversion. This template tests the ability
404
+ to calculate outlet concentrations in an isothermal, isobaric flow system
405
+ using the specialized gas-phase concentration equations from Fogler, which
406
+ account for this volumetric change via the parameter epsilon ($\\epsilon$).
407
+
408
+ $C_A = C_{A0} \\frac{1 - X_A}{1 + \\epsilon X_A}$
409
+ $C_B = C_{A0} \\frac{\\Theta_B - (b/a)X_A}{1 + \\epsilon X_A}$
410
+
411
+ Returns:
412
+ tuple: A tuple containing:
413
+ - str: A question about calculating gas-phase outlet concentrations.
414
+ - str: A detailed solution showing the application of the correct formulas.
415
+ """
416
+ # 1. Randomized Parameters
417
+
418
+ # Select unique names
419
+ reactant_A_name, reactant_B_name = random.sample(GAS_PHASE_REACTANTS, 2)
420
+ product_C_name = random.choice(PRODUCTS)
421
+
422
+ # Stoichiometric coefficients
423
+ a = random.choice([1, 2])
424
+ b = random.randint(1, 3)
425
+ c = random.randint(1, 2)
426
+
427
+ # Initial concentration (gases have lower concentrations, units are mol/dm^3)
428
+ C_A0 = round(random.uniform(0.05, 0.25), 3)
429
+
430
+ # Conversion
431
+ X_A = round(random.uniform(0.50, 0.90), 2)
432
+
433
+ # Theta_B must be in excess of the stoichiometric requirement
434
+ Theta_B = round((b / a) * random.uniform(1.2, 2.5), 2)
435
+
436
+ # Epsilon (ε) can be positive (expansion) or negative (contraction)
437
+ # Ensure it's not too close to zero to make the problem meaningful
438
+ epsilon = 0
439
+ while abs(epsilon) < 0.1:
440
+ epsilon = round(random.uniform(-0.5, 0.5), 2)
441
+
442
+ # 2. Core Calculations
443
+ denominator = 1 + epsilon * X_A
444
+ C_A = C_A0 * (1 - X_A) / denominator
445
+ C_B = C_A0 * (Theta_B - (b / a) * X_A) / denominator
446
+
447
+ # 3. Generate Question and Solution Strings
448
+ def format_species(coeff, name):
449
+ return f"{coeff if coeff > 1 else ''}{name}"
450
+
451
+ reaction_string = (
452
+ f"{format_species(a, reactant_A_name)} + {format_species(b, reactant_B_name)} -> "
453
+ f"{format_species(c, product_C_name)}"
454
+ )
455
+
456
+ question = (
457
+ f"The following elementary gas-phase reaction occurs in a steady-state PFR:\n"
458
+ f"**Reaction:** ${reaction_string}$\n\n"
459
+ f"The reaction is carried out **isothermally** and **isobarically**. The feed enters the reactor with an initial concentration of {reactant_A_name} of $C_{{A0}} = {C_A0}$ mol/dm³.\n\n"
460
+ f"The following parameters are known:\n"
461
+ f"- Molar feed ratio: $\\Theta_B = F_{{B0}}/F_{{A0}} = {Theta_B}$\n"
462
+ f"- Volumetric change parameter: $\\epsilon = {epsilon}$\n\n"
463
+ f"If the reaction achieves a final conversion of $X_A = {X_A}$, what are the outlet concentrations of {reactant_A_name} ($C_A$) and {reactant_B_name} ($C_B$)?"
464
+ )
465
+
466
+ solution = (
467
+ f"**Step 1:** Identify Given Information\n"
468
+ f"- Initial Concentration: $C_{{A0}} = {C_A0}$ mol/dm³\n"
469
+ f"- Conversion: $X_A = {X_A}$\n"
470
+ f"- Molar Feed Ratio: $\\Theta_B = {Theta_B}$\n"
471
+ f"- Volumetric Change Parameter: $\\epsilon = {epsilon}$\n"
472
+ f"- Stoichiometric Ratio: $b/a = {b}/{a} = {round(b/a, 2)}$\n\n"
473
+
474
+ f"**Step 2:** State the Governing Equations\n"
475
+ f"For an isothermal, isobaric gas-phase reaction with a change in the number of moles, the concentration of each species is a function of conversion ($X_A$) and the volumetric change parameter ($\\epsilon$). The term $(1 + \\epsilon X_A)$ in the denominator corrects for the change in volumetric flow rate.\n\n"
476
+ f"For reactant A: \n$C_A = C_{{A0}} \\frac{{1 - X_A}}{{1 + \\epsilon X_A}}$\n\n"
477
+ f"For reactant B: \n$C_B = C_{{A0}} \\frac{{\\Theta_B - (b/a)X_A}}{{1 + \\epsilon X_A}}$\n\n"
478
+
479
+ f"**Step 3:** Calculate the Denominator Term\n"
480
+ f"First, let's calculate the volumetric correction term, which is common to all species:\n"
481
+ f"$1 + \\epsilon X_A = 1 + ({epsilon})({X_A}) = {round(denominator, 4)}$\n\n"
482
+
483
+ f"**Step 4:** Calculate Outlet Concentrations\n\n"
484
+ f"**For {reactant_A_name} ($C_A$):**\n"
485
+ f"$C_A = {C_A0} \\frac{{1 - {X_A}}}{{{round(denominator, 4)}}} = {C_A0} \\frac{{{round(1 - X_A, 2)}}}{{{round(denominator, 4)}}} = \\mathbf{{{round(C_A, 4)}}}$ mol/dm³\n\n"
486
+
487
+ f"**For {reactant_B_name} ($C_B$):**\n"
488
+ f"First, calculate the numerator term for B: \n"
489
+ f"$\\Theta_B - (b/a)X_A = {Theta_B} - ({round(b/a, 2)})({X_A}) = {round(Theta_B - (b/a)*X_A, 4)}$\n"
490
+ f"Now, calculate the concentration:\n"
491
+ f"$C_B = {C_A0} \\frac{{{round(Theta_B - (b/a)*X_A, 4)}}}{{{round(denominator, 4)}}} = \\mathbf{{{round(C_B, 4)}}}$ mol/dm³\n\n"
492
+
493
+ f"**Final Answer**\n"
494
+ f"The outlet concentrations are:\n"
495
+ f"- **$C_A$:** ${round(C_A, 4)}$ mol/dm³\n"
496
+ f"- **$C_B$:** ${round(C_B, 4)}$ mol/dm³"
497
+ )
498
+
499
+ return question, solution
500
+
501
+
502
+ def main():
503
+ """
504
+ Generate numerous instances of each stoichiometry template with different random seeds
505
+ and write the results to a JSONL file.
506
+ """
507
+ import json
508
+ import os
509
+
510
+ # Define the output path (Modify this path according to where you are running the code from)
511
+ output_file = "testset/chemical_engineering/reaction_kinetics/stoichiometry.jsonl"
512
+
513
+ # Create the directory if it doesn't exist
514
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
515
+
516
+ # List of template functions with their ID and level
517
+ templates = [
518
+ (template_batch_moles_vs_conversion, "batch_moles_vs_conversion", "Easy"),
519
+ (template_flow_system_molar_flow_rates, "flow_rates_vs_conversion", "Intermediate"),
520
+ (template_limiting_reactant, "finding_limiting_reactant", "Intermediate"),
521
+ (template_gas_phase_concentration, "gas_phase_concentration", "Advanced"),
522
+ ]
523
+
524
+ # List to store all generated problems
525
+ all_problems = []
526
+
527
+ # Generate problems for each template
528
+ for template_func, id_name, level in templates:
529
+ for _ in range(50):
530
+ # Generate a unique seed for each problem
531
+ seed = random.randint(1_000_000_000, 4_000_000_000)
532
+ random.seed(seed)
533
+
534
+ # Generate the problem and solution
535
+ question, solution = template_func()
536
+
537
+ # Create a JSON entry
538
+ problem_entry = {
539
+ "seed": seed,
540
+ "branch": "chemical_engineering",
541
+ "domain": "reaction_kinetics",
542
+ "area": "stoichiometry",
543
+ "id": id_name,
544
+ "level": level,
545
+ "question": question,
546
+ "solution": solution
547
+ }
548
+
549
+ # Add to the list of problems
550
+ all_problems.append(problem_entry)
551
+
552
+ # Shuffle the problems to mix templates and levels
553
+ random.shuffle(all_problems)
554
+
555
+ # Write all problems to a .jsonl file
556
+ with open(output_file, "w") as file:
557
+ for problem in all_problems:
558
+ file.write(json.dumps(problem))
559
+ file.write("\n")
560
+
561
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
562
+
563
+
564
+ if __name__ == "__main__":
565
+ main()
data/templates/branches/chemical_engineering/thermodynamics/heat_effects.py ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from scipy.optimize import fsolve
3
+ from data.templates.branches.chemical_engineering.constants import SUBSTANCES_FOR_HEATING, SUBSTANCES_FOR_VAPORIZATION, HEATS_OF_FORMATION, REACTIONS, CP_PARAMS, COMBUSTION_REACTIONS
4
+
5
+
6
+ # Template 1 (Easy)
7
+ def template_sensible_heat_constant_cp():
8
+ """
9
+ Sensible Heat Calculation (Constant Heat Capacity)
10
+
11
+ Scenario:
12
+ This template calculates the sensible heat required to change the
13
+ temperature of a substance without changing its phase. It uses the
14
+ substance's actual specific heat capacity (Cp) for a physically
15
+ accurate problem.
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:
27
+ - str: A question asking to compute the sensible heat.
28
+ - str: A step-by-step solution showing the calculation.
29
+ """
30
+ # 1. Parameterize the inputs using the substance list
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
45
+ # Q will be in Joules (g * J/g·K * K)
46
+ Q_J = m * Cp * delta_T
47
+ # Convert to kilojoules for the final answer
48
+ Q_kJ = Q_J / 1000
49
+
50
+ # 3. Generate the question and solution strings
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"
64
+ f"- Specific Heat Capacity (Cp) = {Cp} J/g·K\n"
65
+ f"- Initial Temperature (T1) = {T1_C}°C\n"
66
+ f"- Final Temperature (T2) = {T2_C}°C\n\n"
67
+
68
+ f"**Step 3:** Calculate the temperature difference (ΔT).\n"
69
+ f"A change in temperature has the same magnitude in Celsius and Kelvin.\n"
70
+ f"ΔT = T2 - T1 = {T2_C}°C - {T1_C}°C = {round(delta_T, 1)} K\n\n"
71
+
72
+ f"**Step 4:** Substitute the values into the formula to find the heat in Joules (J).\n"
73
+ f"Q = {m} g * {Cp} J/g·K * {round(delta_T, 1)} K\n"
74
+ f"Q = {round(Q_J, 1)} J\n\n"
75
+
76
+ f"**Step 5:** Convert the heat to kilojoules (kJ) as requested.\n"
77
+ f"Q = {round(Q_J, 1)} J * (1 kJ / 1000 J) = {round(Q_kJ, 2)} kJ\n\n"
78
+
79
+ f"**Answer:** The heat required is **{round(Q_kJ, 2)} kJ**."
80
+ )
81
+
82
+ return question, solution
83
+
84
+
85
+ # Template 2 (Easy)
86
+ def template_latent_heat_vaporization():
87
+ """
88
+ Latent Heat of Vaporization
89
+
90
+ Scenario:
91
+ This template calculates the heat required to cause a phase change
92
+ (vaporization) at a constant temperature and pressure. This energy,
93
+ known as latent heat, is used to overcome intermolecular forces
94
+ rather than to increase the substance's kinetic energy (temperature).
95
+
96
+ The governing equation is:
97
+ Q = n * ΔH_vap
98
+ Where:
99
+ - Q: Total heat absorbed
100
+ - n: Number of moles
101
+ - ΔH_vap: Molar heat of vaporization
102
+
103
+ Returns:
104
+ tuple: A tuple containing:
105
+ - str: A question asking to compute the latent heat.
106
+ - str: A step-by-step solution showing the calculation.
107
+ """
108
+ # 1. Parameterize the inputs using the substance list
109
+ substance_data = random.choice(SUBSTANCES_FOR_VAPORIZATION)
110
+ substance_name = substance_data["name"]
111
+ delta_H_vap = substance_data["delta_H_vap"] # in kJ/mol
112
+
113
+ # Generate a random number of moles
114
+ n = round(random.uniform(0.5, 5.0), 2) # moles
115
+
116
+ # 2. Perform the core calculation
117
+ # The result will be in kJ since ΔH_vap is in kJ/mol
118
+ Q_kJ = n * delta_H_vap
119
+
120
+ # 3. Generate the question and solution strings
121
+ question = (
122
+ f"How much heat in kJ is required to completely vaporize {n} moles of "
123
+ f"liquid {substance_name} at its normal boiling point? The molar heat of "
124
+ f"vaporization for {substance_name} is {delta_H_vap} kJ/mol."
125
+ )
126
+
127
+ solution = (
128
+ f"**Step 1:** State the formula.\n"
129
+ f"Q = n * ΔH_vap\n\n"
130
+
131
+ f"**Step 2:** List the given values.\n"
132
+ f"- Moles (n) = {n} mol\n"
133
+ f"- Molar Heat of Vaporization (ΔH_vap) = {delta_H_vap} kJ/mol\n\n"
134
+
135
+ f"**Step 3:** Substitute the values into the formula.\n"
136
+ f"Q = {n} mol * {delta_H_vap} kJ/mol\n\n"
137
+
138
+ f"**Step 4:** Calculate the total heat required.\n"
139
+ f"Q = {round(Q_kJ, 2)} kJ\n\n"
140
+
141
+ f"**Answer:** The total heat required to vaporize the {substance_name} is **{round(Q_kJ, 2)} kJ**."
142
+ )
143
+
144
+ return question, solution
145
+
146
+
147
+ # Template 3 (Easy)
148
+ def template_heat_of_reaction_formation():
149
+ """
150
+ Standard Heat of Reaction from Heats of Formation
151
+
152
+ Scenario:
153
+ This template applies Hess's Law to find the standard heat of reaction
154
+ at 298.15 K (ΔH_rxn°). It is calculated by subtracting the sum of the
155
+ standard heats of formation (ΔH_f°) of the reactants from the sum of
156
+ the heats of formation of the products, with each being weighted by
157
+ its stoichiometric coefficient (v).
158
+
159
+ The governing equation is:
160
+ ΔH_rxn° = Σ(v * ΔH_f°)products - Σ(v * ΔH_f°)reactants
161
+
162
+ Returns:
163
+ tuple: A tuple containing:
164
+ - str: A question asking to compute the standard heat of reaction.
165
+ - str: A step-by-step solution showing the calculation.
166
+ """
167
+ # 1. Parameterize the inputs by choosing a random reaction
168
+ reaction_data = random.choice(REACTIONS)
169
+ equation = reaction_data["equation"]
170
+ reactants = reaction_data["reactants"]
171
+ products = reaction_data["products"]
172
+
173
+ # 2. Perform the core calculation with full precision
174
+ products_enthalpy = sum(nu * HEATS_OF_FORMATION[species] for species, nu in products.items())
175
+ reactants_enthalpy = sum(nu * HEATS_OF_FORMATION[species] for species, nu in reactants.items())
176
+
177
+ # Final calculation uses the precise, unrounded intermediate values
178
+ delta_H_rxn = products_enthalpy - reactants_enthalpy
179
+
180
+ # 3. Generate the question and solution strings
181
+ required_species = list(reactants.keys()) + list(products.keys())
182
+ hf_list_for_question = "\n".join(
183
+ f"- {s}: {HEATS_OF_FORMATION[s]} kJ/mol" for s in sorted(list(set(required_species)))
184
+ )
185
+
186
+ # Question updated for clarity
187
+ question = (
188
+ f"Calculate the standard enthalpy of reaction, ΔH_rxn°, at 298.15 K (in kJ) for the reaction as written:\n"
189
+ f"{equation}\n\n"
190
+ f"Use the standard heats of formation (ΔH_f°) provided below:\n{hf_list_for_question}"
191
+ )
192
+
193
+ # Helper functions to format the solution steps
194
+ def format_sum(species_dict):
195
+ return " + ".join([f"({nu} * ΔH_f°[{s}])" for s, nu in species_dict.items()])
196
+
197
+ def format_calc(species_dict):
198
+ return " + ".join([f"({nu} * {HEATS_OF_FORMATION[s]})" for s, nu in species_dict.items()])
199
+
200
+ # Solution updated to use correct rounding procedures and units
201
+ solution = (
202
+ f"**Step 1:** State the formula.\n"
203
+ f"ΔH_rxn° = Σ(v * ΔH_f°)products - Σ(v * ΔH_f°)reactants\n\n"
204
+
205
+ f"**Step 2:** Calculate the total enthalpy of the products.\n"
206
+ f"Σ_products = {format_sum(products)}\n"
207
+ f"Σ_products = {format_calc(products)}\n"
208
+ f"Σ_products = {products_enthalpy:.3f} kJ\n\n"
209
+
210
+ f"**Step 3:** Calculate the total enthalpy of the reactants.\n"
211
+ f"Σ_reactants = {format_sum(reactants)}\n"
212
+ f"Σ_reactants = {format_calc(reactants)}\n"
213
+ f"Σ_reactants = {reactants_enthalpy:.3f} kJ\n\n"
214
+
215
+ f"**Step 4:** Calculate the standard enthalpy of reaction using the full-precision values.\n"
216
+ f"ΔH_rxn° = ({products_enthalpy:.3f}) - ({reactants_enthalpy:.3f})\n"
217
+ f"ΔH_rxn° = {round(delta_H_rxn, 2)} kJ\n\n"
218
+
219
+ f"**Answer:** The standard enthalpy of reaction is **{round(delta_H_rxn, 2)} kJ** for the reaction as written."
220
+ )
221
+
222
+ return question, solution
223
+
224
+
225
+ # Template 4 (Intermediate)
226
+ def template_sensible_heat_temp_dependent_cp():
227
+ """
228
+ Sensible Heat (Temperature-Dependent Heat Capacity)
229
+
230
+ Scenario:
231
+ This template provides a more accurate calculation of sensible heat where
232
+ the heat capacity (Cp) is a polynomial function of temperature. The total
233
+ heat required is found by integrating this function over the temperature
234
+ range from T1 to T2.
235
+
236
+ The governing equation is:
237
+ Q = n * integral(Cp(T) dT) from T1 to T2
238
+
239
+ Returns:
240
+ tuple: A tuple containing:
241
+ - str: A question asking to compute the sensible heat via integration.
242
+ - str: A step-by-step solution showing the analytical integration.
243
+ """
244
+ # 1. Parameterize the inputs
245
+ R = 8.314 # J/(mol·K)
246
+ substance_name = random.choice(list(CP_PARAMS.keys()))
247
+ params = CP_PARAMS[substance_name]
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
256
+ def integral_mean_cp_over_r(T):
257
+ term_a = A * T
258
+ term_b = (B / 2) * (T**2)
259
+ term_c = (C / 3) * (T**3)
260
+ term_d = -D / T if T != 0 else 0
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 "
278
+ f"{substance_name} from {T1} K to {T2} K. The molar heat capacity for "
279
+ f"{substance_name} is given by the equation:\n"
280
+ f" Cp/R = {cp_eq_str}\n"
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
+ )
306
+
307
+ return question, solution
308
+
309
+
310
+ # Template 5 (Advanced)
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 "
381
+ f"process is adiabatic and there is no shaft work, estimate the "
382
+ f"adiabatic flame temperature in Kelvin."
383
+ )
384
+
385
+ solution = (
386
+ f"**Step 1:** Write the balanced chemical equation including inert nitrogen from air.\n"
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**."
407
+ )
408
+
409
+ return question, solution
410
+
411
+
412
+ def main():
413
+ """
414
+ Generate numerous instances of each heat effects template with different random seeds
415
+ and write the results to a JSONL file.
416
+ """
417
+ import json
418
+ import os
419
+
420
+ # Define the output path (Modify this path according to where you are running the code from)
421
+ output_file = "testset/chemical_engineering/thermodynamics/heat_effects.jsonl"
422
+
423
+ # Create the directory if it doesn't exist
424
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
425
+
426
+ # List of template functions with their ID and level
427
+ templates = [
428
+ (template_sensible_heat_constant_cp, "sensible_heat_constant_cp", "Easy"),
429
+ (template_latent_heat_vaporization, "latent_heat_vaporization", "Easy"),
430
+ (template_heat_of_reaction_formation, "heat_of_reaction_formation", "Easy"),
431
+ (template_sensible_heat_temp_dependent_cp, "sensible_heat_temp_dependent_cp", "Intermediate"),
432
+ (template_adiabatic_flame_temperature, "adiabatic_flame_temperature", "Advanced"),
433
+ ]
434
+
435
+ # List to store all generated problems
436
+ all_problems = []
437
+
438
+ # Generate problems for each template
439
+ for template_func, id_name, level in templates:
440
+ for _ in range(50):
441
+ # Generate a unique seed for each problem
442
+ seed = random.randint(1_000_000_000, 4_000_000_000)
443
+ random.seed(seed)
444
+
445
+ # Generate the problem and solution
446
+ question, solution = template_func()
447
+
448
+ # Create a JSON entry
449
+ problem_entry = {
450
+ "seed": seed,
451
+ "branch": "chemical_engineering",
452
+ "domain": "thermodynamics",
453
+ "area": "heat_effects",
454
+ "id": id_name,
455
+ "level": level,
456
+ "question": question,
457
+ "solution": solution
458
+ }
459
+
460
+ # Add to the list of problems
461
+ all_problems.append(problem_entry)
462
+
463
+ # Shuffle the problems to mix templates and levels
464
+ random.shuffle(all_problems)
465
+
466
+ # Write all problems to a .jsonl file
467
+ with open(output_file, "w") as file:
468
+ for problem in all_problems:
469
+ file.write(json.dumps(problem))
470
+ file.write("\n")
471
+
472
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
473
+
474
+
475
+ if __name__ == "__main__":
476
+ main()
data/templates/branches/chemical_engineering/thermodynamics/volumetric_properties_pure_fluids.py ADDED
@@ -0,0 +1,701 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
8
+ def template_ideal_gas_volume():
9
+ """
10
+ Ideal Gas Law Volume Calculation
11
+
12
+ Scenario:
13
+ The Ideal Gas Law, PV = nRT, is a fundamental equation of state that
14
+ describes the behavior of many gases. In this scenario, the pressure,
15
+ temperature, and number of moles of a gas are provided. The objective
16
+ is to apply the Ideal Gas Law to calculate the volume the gas occupies
17
+ using the formula:
18
+
19
+ V = nRT / P
20
+
21
+ Returns:
22
+ tuple: A tuple containing:
23
+ - str: A question asking to compute the gas volume.
24
+ - str: A step-by-step solution showing the calculation.
25
+ """
26
+ # 1. Parameterize the inputs with random values
27
+ gas_name = random.choice(GAS_PHASE_REACTANTS)
28
+ # Moles of gas
29
+ n = round(random.uniform(0.5, 5.0), 2)
30
+ # Temperature in Kelvin
31
+ T_k = round(random.uniform(273.15, 500.0), 2)
32
+ # Pressure in Pascals (for calculation)
33
+ P_pa = round(random.uniform(100000, 500000))
34
+ # Pressure in Kilopascals (for the question text)
35
+ P_kpa = round(P_pa / 1000, 1)
36
+
37
+ # Define the ideal gas constant in SI units
38
+ R = 8.314 # Pa·m³/(mol·K)
39
+
40
+ # 2. Perform the core calculation
41
+ # Volume will be in cubic meters (m³)
42
+ V_m3 = (n * R * T_k) / P_pa
43
+ # Convert volume to Liters for the final answer
44
+ V_L = V_m3 * 1000
45
+
46
+ # 3. Generate the question and solution strings
47
+ question = (
48
+ f"Calculate the volume in liters occupied by {n} moles of {gas_name} gas "
49
+ f"at a temperature of {T_k} K and a pressure of {P_kpa} kPa. "
50
+ f"Assume the gas behaves ideally."
51
+ )
52
+
53
+ solution = (
54
+ f"**Step 1:** State the Ideal Gas Law formula solved for volume (V).\n"
55
+ f"The formula is V = \\frac{{nRT}}{{P}}\n\n"
56
+
57
+ f"**Step 2:** List the given values and the ideal gas constant, ensuring consistent SI units.\n"
58
+ f"- Moles (n) = {n} mol\n"
59
+ f"- Temperature (T) = {T_k} K\n"
60
+ f"- Pressure (P) = {P_kpa} kPa = {P_pa} Pa\n"
61
+ f"- Ideal Gas Constant (R) = {R} Pa·m³/(mol·K)\n\n"
62
+
63
+ f"**Step 3:** Substitute the values into the equation.\n"
64
+ f"V = \\frac{{({n} \\text{{ mol}}) \\times ({R} \\text{{ Pa·m³/mol·K}}) \\times ({T_k} \\text{{ K}})}}{{{P_pa} \\text{{ Pa}}}}\n\n"
65
+
66
+ f"**Step 4:** Calculate the volume in cubic meters.\n"
67
+ f"V = {round(V_m3, 5)} \\text{{ m³}}\n\n"
68
+
69
+ f"**Step 5:** Convert the volume to liters as requested.\n"
70
+ f"Since 1 \\text{{ m³}} = 1000 \\text{{ L}}:\n"
71
+ f"V = {round(V_m3, 5)} \\text{{ m³}} \\times 1000 \\frac{{\\text{{L}}}}{{\\text{{m³}}}} = {round(V_L, 2)} \\text{{ L}}\n\n"
72
+
73
+ f"**Answer:** The volume occupied by the gas is **{round(V_L, 2)} liters**."
74
+ )
75
+
76
+ return question, solution
77
+
78
+
79
+ # Template 2 (Easy)
80
+ def template_two_phase_specific_volume():
81
+ """
82
+ Specific Volume of a Two-Phase Mixture
83
+
84
+ Scenario:
85
+ When a substance exists as a mixture of liquid and vapor in equilibrium
86
+ (a saturated mixture), its overall specific volume (V) depends on the
87
+ proportions of the liquid and vapor phases. This proportion is defined
88
+ by the quality (x), which is the mass fraction of vapor. This template
89
+ provides the specific volumes of the saturated liquid (V^l) and
90
+ saturated vapor (V^v) along with the quality, and asks to calculate
91
+ the overall specific volume.
92
+
93
+ The governing equation is:
94
+ V = (1-x)V^l + xV^v
95
+
96
+ Returns:
97
+ tuple: A tuple containing:
98
+ - str: A question asking to compute the mixture's specific volume.
99
+ - str: A step-by-step solution showing the calculation.
100
+ """
101
+ # 1. Parameterize the inputs with random values
102
+ substance = random.choice(THERMO_SUBSTANCES)
103
+ # Specific volume of saturated liquid in m³/kg
104
+ V_l = round(random.uniform(0.001, 0.002), 5)
105
+ # Specific volume of saturated vapor in m³/kg
106
+ V_v = round(random.uniform(0.05, 2.0), 3)
107
+ # Quality (mass fraction of vapor)
108
+ x = round(random.uniform(0.1, 0.9), 2)
109
+
110
+ # 2. Perform the core calculation
111
+ V = (1 - x) * V_l + x * V_v
112
+
113
+ # 3. Generate the question and solution strings
114
+ question = (
115
+ f"A closed vessel contains a saturated mixture of {substance} at a constant pressure. "
116
+ f"The specific volume of the saturated liquid is {V_l} m³/kg and the "
117
+ f"specific volume of the saturated vapor is {V_v} m³/kg. If the quality "
118
+ f"of the mixture is {x*100:.0f}%, what is the overall specific volume of the mixture?"
119
+ )
120
+
121
+ solution = (
122
+ f"**Step 1:** State the formula for the specific volume of a saturated mixture.\n"
123
+ f"The formula is V = (1-x)V^l + xV^v, where V^l is the saturated liquid specific volume and V^v is the saturated vapor specific volume.\n\n"
124
+
125
+ f"**Step 2:** List the given values.\n"
126
+ f"- Quality (x) = {x}\n"
127
+ f"- Saturated liquid specific volume (V^l) = {V_l} m³/kg\n"
128
+ f"- Saturated vapor specific volume (V^v) = {V_v} m³/kg\n\n"
129
+
130
+ f"**Step 3:** Substitute the values into the equation.\n"
131
+ f"V = (1 - {x})({V_l} \\text{{ m³/kg}}) + ({x})({V_v} \\text{{ m³/kg}})\n"
132
+ f"V = ({(1-x):.2f})({V_l}) + ({x})({V_v})\n"
133
+ f"V = {round((1-x)*V_l, 5)} + {round(x*V_v, 5)}\n\n"
134
+
135
+ f"**Step 4:** Calculate the final specific volume.\n"
136
+ f"V = {round(V, 5)} \\text{{ m³/kg}}\n\n"
137
+
138
+ f"**Answer:** The overall specific volume of the mixture is **{round(V, 5)} m³/kg**."
139
+ )
140
+
141
+ return question, solution
142
+
143
+
144
+ # Template 3 (Easy)
145
+ def template_rackett_equation_volume():
146
+ """
147
+ Rackett Equation for Saturated Liquid Volume
148
+
149
+ Scenario:
150
+ The Rackett equation is a generalized correlation used to estimate the
151
+ molar volume of a saturated liquid when experimental data is not
152
+ available. It relies on the substance's critical properties.
153
+
154
+ This template provides the critical temperature (Tc), critical volume (Vc),
155
+ and critical compressibility factor (Zc) for a substance. The goal is to
156
+ calculate the saturated liquid molar volume (V_sat) at a given
157
+ temperature (T).
158
+
159
+ The governing equation is:
160
+ V_sat = Vc * Zc**((1 - Tr)**0.2857)
161
+ Where:
162
+ - V_sat: Molar volume of the saturated liquid (cm³/mol)
163
+ - Vc: Critical volume (cm³/mol)
164
+ - Zc: Critical compressibility factor
165
+ - Tr: Reduced temperature (T / Tc)
166
+
167
+ Returns:
168
+ tuple: A tuple containing:
169
+ - str: A question asking to compute the saturated liquid volume.
170
+ - str: A step-by-step solution showing the calculation.
171
+ """
172
+ # 1. Parameterize the inputs with random values
173
+ substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
174
+ properties = CRITICAL_PROPERTIES[substance_name]
175
+ Tc = properties["Tc"]
176
+ Vc = properties["Vc"]
177
+ Zc = properties["Zc"]
178
+
179
+ # Generate a random temperature below the critical temperature
180
+ T = round(random.uniform(0.5 * Tc, 0.95 * Tc), 2)
181
+
182
+ # 2. Perform the core calculation
183
+ # Calculate the reduced temperature
184
+ Tr = T / Tc
185
+ # Apply the Rackett equation
186
+ exponent = (1 - Tr)**0.2857
187
+ V_sat = Vc * (Zc**exponent)
188
+
189
+ # 3. Generate the question and solution strings
190
+ question = (
191
+ f"Estimate the molar volume of saturated liquid {substance_name} at {T} K "
192
+ f"using the Rackett equation. The critical properties for {substance_name} are:\n"
193
+ f"Tc = {Tc} K\n"
194
+ f"Vc = {Vc} cm³/mol\n"
195
+ f"Zc = {Zc}"
196
+ )
197
+
198
+ solution = (
199
+ f"**Step 1:** State the Rackett equation.\n"
200
+ f"V_sat = Vc * Zc**((1 - Tr)**0.2857)\n\n"
201
+
202
+ f"**Step 2:** List the given properties and calculate the reduced temperature (Tr).\n"
203
+ f"- Critical Temperature (Tc) = {Tc} K\n"
204
+ f"- Critical Volume (Vc) = {Vc} cm³/mol\n"
205
+ f"- Critical Compressibility (Zc) = {Zc}\n"
206
+ f"- Temperature (T) = {T} K\n\n"
207
+ f"Tr = T / Tc = {T} / {Tc} = {round(Tr, 4)}\n\n"
208
+
209
+ f"**Step 3:** Substitute the values into the Rackett equation.\n"
210
+ f"V_sat = {Vc} * {Zc}**((1 - {round(Tr, 4)})**0.2857)\n"
211
+ f"V_sat = {Vc} * {Zc}**({round(1 - Tr, 4)}**0.2857)\n"
212
+ f"V_sat = {Vc} * {Zc}**({round(exponent, 4)})\n"
213
+ f"V_sat = {Vc} * {round(Zc**exponent, 4)}\n\n"
214
+
215
+ f"**Step 4:** Calculate the final molar volume.\n"
216
+ f"V_sat = {round(V_sat, 2)} cm³/mol\n\n"
217
+
218
+ f"**Answer:** The estimated molar volume of saturated liquid {substance_name} at {T} K is **{round(V_sat, 2)} cm³/mol**."
219
+ )
220
+
221
+ return question, solution
222
+
223
+
224
+ # Template 4 (Intermediate)
225
+ def template_vdw_solve_for_pressure():
226
+ """
227
+ Van der Waals Equation for Pressure Calculation
228
+
229
+ Scenario:
230
+ The van der Waals equation is an equation of state that improves upon the
231
+ ideal gas law by including terms for intermolecular attraction ('a') and
232
+ molecular volume ('b'). This makes it applicable to real fluids in both
233
+ liquid and gas phases.
234
+
235
+ This template provides the critical properties (Tc and Pc) for a substance,
236
+ along with a given temperature (T) and molar volume (V). The objective is
237
+ to first calculate the van der Waals parameters 'a' and 'b', and then
238
+ use them to find the pressure (P).
239
+
240
+ The governing equations are:
241
+ P = (R * T) / (V - b) - a / (V**2)
242
+ a = (27 * R**2 * Tc**2) / (64 * Pc)
243
+ b = (R * Tc) / (8 * Pc)
244
+
245
+ Returns:
246
+ tuple: A tuple containing:
247
+ - str: A question asking to compute the pressure.
248
+ - str: A step-by-step solution showing the calculation.
249
+ """
250
+ # 1. Parameterize the inputs
251
+ # Gas constant in L·bar/(mol·K)
252
+ R = 0.08314
253
+
254
+ substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
255
+ properties = CRITICAL_PROPERTIES[substance_name]
256
+ Tc = properties["Tc"]
257
+ Pc = properties["Pc"]
258
+
259
+ # Generate a random temperature, avoiding the critical region (0.95Tc to 1.05Tc)
260
+ while True:
261
+ T = round(random.uniform(0.8 * Tc, 2.5 * Tc), 2)
262
+ # Avoid temperatures too close to critical point where VdW equation is less accurate
263
+ if not (0.95 * Tc <= T <= 1.05 * Tc):
264
+ break
265
+
266
+ # First, calculate 'b' to ensure V > b
267
+ b = (R * Tc) / (8 * Pc)
268
+ # Generate a random molar volume greater than b
269
+ V = round(random.uniform(1.5 * b, 150 * b), 4)
270
+
271
+ # 2. Perform the core calculation
272
+ # Calculate parameter 'a'
273
+ a = (27 * (R**2) * (Tc**2)) / (64 * Pc)
274
+
275
+ # Calculate pressure using the Van der Waals equation
276
+ P = (R * T) / (V - b) - a / (V**2)
277
+
278
+ # 3. Generate the question and solution strings
279
+ question = (
280
+ f"Using the van der Waals equation of state, calculate the pressure in bar "
281
+ f"exerted by {substance_name} at a temperature of {T} K and a molar volume "
282
+ f"of {V} L/mol. The critical constants for {substance_name} are:\n"
283
+ f"Tc = {Tc} K\n"
284
+ f"Pc = {Pc} bar"
285
+ )
286
+
287
+ solution = (
288
+ f"**Step 1:** State the necessary formulas.\n"
289
+ f"Pressure: P = (R * T) / (V - b) - a / (V**2)\n"
290
+ f"Parameter 'a': a = (27 * R**2 * Tc**2) / (64 * Pc)\n"
291
+ f"Parameter 'b': b = (R * Tc) / (8 * Pc)\n\n"
292
+
293
+ f"**Step 2:** List the given values and the gas constant.\n"
294
+ f"- Temperature (T) = {T} K\n"
295
+ f"- Molar Volume (V) = {V} L/mol\n"
296
+ f"- Critical Temperature (Tc) = {Tc} K\n"
297
+ f"- Critical Pressure (Pc) = {Pc} bar\n"
298
+ f"- Gas Constant (R) = {R} L·bar/(mol·K)\n\n"
299
+
300
+ f"**Step 3:** Calculate the co-volume parameter 'b'.\n"
301
+ f"b = ({R} * {Tc}) / (8 * {Pc}) = {round(b, 5)} L/mol\n\n"
302
+
303
+ f"**Step 4:** Calculate the attraction parameter 'a'.\n"
304
+ f"a = (27 * ({R})**2 * ({Tc})**2) / (64 * {Pc}) = {round(a, 4)} L²·bar/mol²\n\n"
305
+
306
+ f"**Step 5:** Substitute all values into the van der Waals equation to find P.\n"
307
+ f"P = ({R} * {T}) / ({V} - {round(b, 5)}) - {round(a, 4)} / ({V})**2\n"
308
+ f"P = {round(R * T, 2)} / {round(V - b, 5)} - {round(a, 4)} / {round(V**2, 5)}\n"
309
+ f"P = {round((R * T) / (V - b), 2)} - {round(a / (V**2), 2)}\n\n"
310
+
311
+ f"**Step 6:** Calculate the final pressure.\n"
312
+ f"P = {round(P, 2)} bar\n\n"
313
+
314
+ f"**Answer:** The pressure exerted by the {substance_name} is **{round(P, 2)} bar**."
315
+ )
316
+
317
+ return question, solution
318
+
319
+
320
+ # Templat 5 (Intermediate)
321
+ def template_pitzer_correlation_z():
322
+ """
323
+ Compressibility Factor from Pitzer's Correlation
324
+
325
+ Scenario:
326
+ The Pitzer correlation is a widely used application of the principle of
327
+ corresponding states to estimate the properties of real fluids. It
328
+ improves upon two-parameter correlations by introducing the acentric
329
+ factor (omega), which accounts for the non-sphericity of molecules.
330
+
331
+ This template uses a simplified form of the Pitzer correlation, valid
332
+ for gases at low to moderate pressures, to calculate the compressibility
333
+ factor (Z).
334
+
335
+ The governing equations are:
336
+ Z = 1 + (Pr / Tr) * (B0 + omega * B1)
337
+ B0 = 0.083 - (0.422 / Tr**1.6)
338
+ B1 = 0.139 - (0.172 / Tr**4.2)
339
+ Where:
340
+ - Z: Compressibility factor
341
+ - Tr, Pr: Reduced temperature and pressure
342
+ - omega: Acentric factor
343
+ - B0, B1: Second virial coefficients
344
+
345
+ Returns:
346
+ tuple: A tuple containing:
347
+ - str: A question asking to compute the compressibility factor.
348
+ - str: A step-by-step solution showing the calculation.
349
+ """
350
+ # 1. Parameterize the inputs
351
+ R = 0.08314 # L·bar/(mol·K)
352
+
353
+ substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
354
+ properties = CRITICAL_PROPERTIES[substance_name]
355
+ Tc = properties["Tc"]
356
+ Pc = properties["Pc"]
357
+ omega = properties["omega"]
358
+
359
+ # Generate random T and P in the gas phase (Tr > 1) and at low-moderate pressure
360
+ Tr = round(random.uniform(1.1, 3.0), 3)
361
+ Pr = round(random.uniform(0.1, 2.0), 3)
362
+ T = round(Tr * Tc, 2)
363
+ P = round(Pr * Pc, 2)
364
+
365
+ # 2. Perform the core calculation
366
+ # Calculate virial coefficients
367
+ B0 = 0.083 - 0.422 / (Tr**1.6)
368
+ B1 = 0.139 - 0.172 / (Tr**4.2)
369
+ # Calculate compressibility factor
370
+ Z = 1 + (Pr / Tr) * (B0 + omega * B1)
371
+ # Optional extension: Calculate molar volume
372
+ V = (Z * R * T) / P
373
+
374
+ # 3. Generate the question and solution strings
375
+ question = (
376
+ f"For {substance_name} at a temperature of {T} K and a pressure of {P} bar, "
377
+ f"determine the compressibility factor, Z, using the Pitzer correlation for the "
378
+ f"second virial coefficient. The properties for {substance_name} are:\n"
379
+ f"Critical Temperature (Tc) = {Tc} K\n"
380
+ f"Critical Pressure (Pc) = {Pc} bar\n"
381
+ f"Acentric Factor (ω) = {omega}"
382
+ )
383
+
384
+ solution = (
385
+ f"**Step 1:** State the governing formulas.\n"
386
+ f"Z = 1 + (Pr / Tr) * (B0 + omega * B1)\n"
387
+ f"B0 = 0.083 - (0.422 / Tr**1.6)\n"
388
+ f"B1 = 0.139 - (0.172 / Tr**4.2)\n\n"
389
+
390
+ f"**Step 2:** Calculate the reduced temperature (Tr) and reduced pressure (Pr).\n"
391
+ f"Tr = T / Tc = {T} K / {Tc} K = {round(Tr, 4)}\n"
392
+ f"Pr = P / Pc = {P} bar / {Pc} bar = {round(Pr, 4)}\n\n"
393
+
394
+ f"**Step 3:** Calculate the virial equation coefficients, B0 and B1.\n"
395
+ f"B0 = 0.083 - (0.422 / {round(Tr, 4)}**1.6) = {round(B0, 4)}\n"
396
+ f"B1 = 0.139 - (0.172 / {round(Tr, 4)}**4.2) = {round(B1, 4)}\n\n"
397
+
398
+ f"**Step 4:** Substitute all values to calculate the compressibility factor (Z).\n"
399
+ f"Z = 1 + ({round(Pr, 4)} / {round(Tr, 4)}) * ({round(B0, 4)} + {omega} * {round(B1, 4)})\n"
400
+ f"Z = 1 + {round(Pr / Tr, 4)} * ({round(B0 + omega * B1, 4)})\n"
401
+ f"Z = {round(Z, 4)}\n\n"
402
+
403
+ f"(Optional) **Step 5:** Calculate the molar volume (V).\n"
404
+ f"V = Z * R * T / P = ({round(Z, 4)} * {R} * {T}) / {P} = {round(V, 4)} L/mol\n\n"
405
+
406
+ f"**Answer:** The compressibility factor, Z, for {substance_name} at the given conditions is **{round(Z, 4)}**."
407
+ )
408
+
409
+ return question, solution
410
+
411
+
412
+ # Template 6 (Advanced)
413
+ def template_vdw_solve_for_volume():
414
+ """
415
+ Molar Volume from the Van der Waals Equation
416
+
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
+
525
+ # Template 7 (Advanced)
526
+ def template_work_isothermal_virial():
527
+ """
528
+ Work of Isothermal Compression for a Virial Gas
529
+
530
+ Scenario:
531
+ Calculating the work (W) for a mechanically reversible, isothermal
532
+ process requires integrating the pressure (P) with respect to volume (V).
533
+ For a non-ideal gas described by the virial equation, the P-V
534
+ relationship is more complex than the ideal gas law, leading to a
535
+ different result for the work of compression.
536
+
537
+ This template calculates work by first determining the second virial
538
+ coefficient (B) and the initial/final state properties (V1, V2), and
539
+ then applying the analytical integral of the virial equation.
540
+
541
+ The governing equations are:
542
+ P = R*T * (1/V + B/V**2)
543
+ W = - integral(P dV) from V1 to V2
544
+ W = -[R*T*ln(V2/V1) - B*R*T*(1/V2 - 1/V1)]
545
+
546
+ Returns:
547
+ tuple: A tuple containing:
548
+ - str: A question asking to compute the work of compression.
549
+ - str: A step-by-step solution showing the calculation and comparison
550
+ to the ideal gas case.
551
+ """
552
+ # 1. Parameterize the inputs
553
+ R = 0.08314 # L·bar/(mol·K)
554
+
555
+ substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
556
+ properties = CRITICAL_PROPERTIES[substance_name]
557
+ Tc = properties["Tc"]
558
+ Pc = properties["Pc"]
559
+ omega = properties["omega"]
560
+
561
+ # Generate conditions in the gas phase (Tr > 1) at moderate pressures
562
+ Tr = round(random.uniform(1.2, 3.0), 3)
563
+ T = round(Tr * Tc, 2)
564
+
565
+ P1_r = round(random.uniform(0.5, 2.0), 2)
566
+ P2_r = round(random.uniform(2.5, 5.0), 2)
567
+ P1 = round(P1_r * Pc, 2)
568
+ P2 = round(P2_r * Pc, 2)
569
+
570
+ # 2. Perform the core calculation
571
+ # Calculate B
572
+ B0 = 0.083 - 0.422 / (Tr**1.6)
573
+ B1 = 0.139 - 0.172 / (Tr**4.2)
574
+ B = (R * Tc / Pc) * (B0 + omega * B1) # Units: L/mol
575
+
576
+ # Calculate initial and final states
577
+ Z1 = 1 + (B * P1) / (R * T)
578
+ Z2 = 1 + (B * P2) / (R * T)
579
+ V1 = (Z1 * R * T) / P1
580
+ V2 = (Z2 * R * T) / P2
581
+
582
+ # Calculate work using the integrated virial equation
583
+ term1 = R * T * math.log(V2 / V1)
584
+ term2 = -B * R * T * ((1/V2) - (1/V1))
585
+ W_virial_Lbar = -(term1 + term2)
586
+ W_virial_J = W_virial_Lbar * 100 # Convert L·bar to Joules
587
+
588
+ # For comparison, calculate ideal gas work
589
+ W_ideal_Lbar = -R * T * math.log(P1 / P2)
590
+ W_ideal_J = W_ideal_Lbar * 100
591
+
592
+ # 3. Generate the question and solution strings
593
+ question = (
594
+ f"Calculate the work in J/mol required to isothermally and reversibly "
595
+ f"compress 1 mole of {substance_name} from {P1} bar to {P2} bar at a "
596
+ f"constant temperature of {T} K. Base your calculation on the virial "
597
+ f"equation of state truncated to two terms. The properties for {substance_name} are:\n"
598
+ f"Tc = {Tc} K, Pc = {Pc} bar, ω = {omega}"
599
+ )
600
+
601
+ solution = (
602
+ f"**Step 1:** Define the pressure from the virial equation and find the analytical integral for work.\n"
603
+ f"P = RT(1/V + B/V²)\n"
604
+ f"The integrated form is: W = -[RT·ln(V2/V1) - BRT(1/V2 - 1/V1)]\n\n"
605
+
606
+ f"**Step 2:** Calculate the second virial coefficient (B) at T = {T} K.\n"
607
+ f"Reduced Temperature, Tr = T/Tc = {T}/{Tc} = {Tr}\n"
608
+ f"B0 = 0.083 - 0.422 / ({Tr})**1.6 = {round(B0, 4)}\n"
609
+ f"B1 = 0.139 - 0.172 / ({Tr})**4.2 = {round(B1, 4)}\n"
610
+ f"B = (R·Tc/Pc) * (B0 + ω·B1) = {round(B, 5)} L/mol\n\n"
611
+
612
+ f"**Step 3:** Determine the initial (V1) and final (V2) molar volumes.\n"
613
+ f"Z1 = 1 + B·P1/(R·T) = 1 + ({round(B, 5)}*{P1})/({R}*{T}) = {round(Z1, 4)}\n"
614
+ f"V1 = Z1·R·T/P1 = {round(V1, 5)} L/mol\n"
615
+ f"Z2 = 1 + B·P2/(R·T) = 1 + ({round(B, 5)}*{P2})/({R}*{T}) = {round(Z2, 4)}\n"
616
+ f"V2 = Z2·R·T/P2 = {round(V2, 5)} L/mol\n\n"
617
+
618
+ f"**Step 4:** Substitute V1 and V2 into the integrated work equation.\n"
619
+ f"W = -[{round(R*T, 2)}·ln({round(V2, 5)}/{round(V1, 5)}) - {round(B*R*T, 3)}(1/{round(V2, 5)} - 1/{round(V1, 5)})]\n"
620
+ f"W = -[{round(term1, 2)} + {round(term2, 2)}] = {round(W_virial_Lbar, 2)} L·bar/mol\n\n"
621
+
622
+ f"**Step 5:** Convert the work to the required units (J/mol).\n"
623
+ f"Since 1 L·bar = 100 J:\n"
624
+ f"W = {round(W_virial_Lbar, 2)} L·bar/mol * 100 J/(L·bar) = {round(W_virial_J, 0)} J/mol\n\n"
625
+
626
+ f"**For Comparison:** The work required for an ideal gas is W_ideal = -RT·ln(P1/P2) = {round(W_ideal_J, 0)} J/mol. "
627
+ f"The deviation shows the effect of intermolecular forces accounted for by the virial equation.\n\n"
628
+
629
+ f"**Answer:** The required work of compression is approximately **{round(W_virial_J, 0)} J/mol**."
630
+ )
631
+
632
+ return question, solution
633
+
634
+
635
+ def main():
636
+ """
637
+ Generate numerous instances of each volumetric properties of pure fluids template with
638
+ different random seeds and write the results to a JSONL file.
639
+ """
640
+ import json
641
+ import os
642
+
643
+ # Define the output path (Modify this path according to where you are running the code from)
644
+ output_file = "testset/chemical_engineering/thermodynamics/volumetric_properties_pure_fluids.jsonl"
645
+
646
+ # Create the directory if it doesn't exist
647
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
648
+
649
+ # List of template functions with their ID and level
650
+ templates = [
651
+ (template_ideal_gas_volume, "ideal_gas_volume", "Easy"),
652
+ (template_two_phase_specific_volume, "two_phase_specific_volume", "Easy"),
653
+ (template_rackett_equation_volume, "rackett_equation_volume", "Easy"),
654
+ (template_vdw_solve_for_pressure, "vdw_solve_for_pressure", "Intermediate"),
655
+ (template_pitzer_correlation_z, "pitzer_correlation_z", "Intermediate"),
656
+ (template_vdw_solve_for_volume, "vdw_solve_for_volume", "Advanced"),
657
+ (template_work_isothermal_virial, "work_isothermal_virial", "Advanced"),
658
+ ]
659
+
660
+ # List to store all generated problems
661
+ all_problems = []
662
+
663
+ # Generate problems for each template
664
+ for template_func, id_name, level in templates:
665
+ for _ in range(50):
666
+ # Generate a unique seed for each problem
667
+ seed = random.randint(1_000_000_000, 4_000_000_000)
668
+ random.seed(seed)
669
+
670
+ # Generate the problem and solution
671
+ question, solution = template_func()
672
+
673
+ # Create a JSON entry
674
+ problem_entry = {
675
+ "seed": seed,
676
+ "branch": "chemical_engineering",
677
+ "domain": "thermodynamics",
678
+ "area": "volumetric_properties_pure_fluids",
679
+ "id": id_name,
680
+ "level": level,
681
+ "question": question,
682
+ "solution": solution
683
+ }
684
+
685
+ # Add to the list of problems
686
+ all_problems.append(problem_entry)
687
+
688
+ # Shuffle the problems to mix templates and levels
689
+ random.shuffle(all_problems)
690
+
691
+ # Write all problems to a .jsonl file
692
+ with open(output_file, "w") as file:
693
+ for problem in all_problems:
694
+ file.write(json.dumps(problem))
695
+ file.write("\n")
696
+
697
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
698
+
699
+
700
+ if __name__ == "__main__":
701
+ main()
data/templates/branches/chemical_engineering/transport_phenomena/shell_momentum_balances.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ from data.templates.branches.chemical_engineering.constants import COMMON_LIQUIDS, GRAVITATIONAL_ACCELERATION
4
+
5
+
6
+ # Template 1 (Easy)
7
+ def template_falling_film_max_velocity():
8
+ """
9
+ Falling Film Maximum Velocity Calculation
10
+
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)
20
+
21
+ Returns:
22
+ tuple: A tuple containing:
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 = (
53
+ f"A thin film of {fluid_name} is flowing down a flat surface inclined at an "
54
+ f"angle of {inclination_angle_deg} degrees from the vertical. The film thickness is "
55
+ f"measured to be {film_thickness_mm} mm.\n\n"
56
+ f"Given the fluid's density is {fluid_density} kg/m^3 and its viscosity is "
57
+ f"{fluid_viscosity} Pa·s, calculate the maximum velocity of the film (at the "
58
+ f"liquid-air interface). Assume the flow is laminar.\n\n"
59
+ f"Use a value of g = {g} m/s^2."
60
+ )
61
+
62
+ solution = (
63
+ f"**Step 1:** Identify Given Information\n"
64
+ f"First, we list the known values from the problem statement.\n"
65
+ f"- Fluid: {fluid_name}\n"
66
+ f"- Fluid Density (rho): {fluid_density} kg/m^3\n"
67
+ f"- Fluid Viscosity (mu): {fluid_viscosity} Pa·s\n"
68
+ f"- Film Thickness (delta): {film_thickness_mm} mm\n"
69
+ f"- Inclination Angle (beta): {inclination_angle_deg} degrees\n"
70
+ f"- Gravitational Acceleration (g): {g} m/s^2\n\n"
71
+
72
+ f"**Step 2:** Perform Unit Conversions\n"
73
+ f"The core equation requires all units to be in the SI base system. We must convert the film thickness from millimeters to meters.\n"
74
+ f"delta = {film_thickness_mm} mm * (1 m / 1000 mm) = {film_thickness_m} m\n\n"
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"
82
+ f"v_z_max = (({fluid_density} kg/m^3) * ({g} m/s^2) * ({film_thickness_m} m)^2 * cos({inclination_angle_deg} deg)) / (2 * ({fluid_viscosity} Pa·s))\n\n"
83
+
84
+ f"**Step 5:** Calculate the Final Velocity\n"
85
+ f"Performing the calculation gives the maximum velocity.\n"
86
+ f"v_z_max = {round(v_max, 5)} m/s\n\n"
87
+
88
+ f"**Answer:** The maximum velocity of the {fluid_name} film is {round(v_max, 5)} m/s."
89
+ )
90
+
91
+ return question, solution
92
+
93
+
94
+ # Template 2 (Easy)
95
+ def template_hagen_poiseuille_flowrate():
96
+ """
97
+ Hagen-Poiseuille Volumetric Flow Rate Calculation
98
+
99
+ Scenario:
100
+ This template calculates the volumetric flow rate (Q) for a fluid
101
+ experiencing laminar flow through a straight, circular pipe of
102
+ constant cross-section. It's based on the exact solution for
103
+ pressure-driven flow derived from a shell momentum balance.
104
+
105
+ Core Equation (Hagen-Poiseuille Equation):
106
+ Q = (pi * (P0 - PL) * R^4) / (8 * mu * L)
107
+
108
+ Returns:
109
+ tuple: A tuple containing:
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 = (
140
+ f"A fluid, {fluid_name}, is flowing through a smooth circular pipe. "
141
+ f"The pipe has an inner radius of {pipe_radius_cm} cm and a total length of {pipe_length_m} m. "
142
+ f"The pressure drop across the length of the pipe is measured to be {pressure_drop_kPa} kPa.\n\n"
143
+ f"Given that the viscosity of {fluid_name} is approximately {fluid_viscosity_cP:.2f} cP, "
144
+ f"calculate the volumetric flow rate (Q) in m^3/s. Assume the flow is laminar."
145
+ )
146
+
147
+ solution = (
148
+ f"**Step 1:** Identify Given Information\n"
149
+ f"First, we list the known values from the problem statement.\n"
150
+ f"- Fluid: {fluid_name}\n"
151
+ f"- Pipe Radius (R): {pipe_radius_cm} cm\n"
152
+ f"- Pipe Length (L): {pipe_length_m} m\n"
153
+ f"- Pressure Drop (P0 - PL): {pressure_drop_kPa} kPa\n"
154
+ f"- Fluid Viscosity (mu): {fluid_viscosity_cP:.2f} cP\n\n"
155
+
156
+ f"**Step 2:** Perform Unit Conversions\n"
157
+ f"The Hagen-Poiseuille equation requires all units to be in the SI base system. We must convert the radius, pressure drop, and viscosity.\n"
158
+ f"- Radius: R = {pipe_radius_cm} cm * (1 m / 100 cm) = {pipe_radius_m} m\n"
159
+ f"- Pressure Drop: P0 - PL = {pressure_drop_kPa} kPa * (1000 Pa / 1 kPa) = {pressure_drop_Pa} Pa\n"
160
+ f"- Viscosity: mu = {fluid_viscosity_cP:.2f} cP * (1 Pa·s / 1000 cP) = {fluid_viscosity_Pas} Pa·s\n\n"
161
+
162
+ f"**Step 3:** State the Core Equation\n"
163
+ f"The volumetric flow rate (Q) for laminar flow in a circular pipe is given by the Hagen-Poiseuille equation:\n"
164
+ f"Q = (pi * (P0 - PL) * R^4) / (8 * mu * L)\n\n"
165
+
166
+ f"**Step 4:** Substitute Values into the Equation\n"
167
+ f"Now, we substitute the converted SI values into the equation.\n"
168
+ f"Q = (pi * ({pressure_drop_Pa} Pa) * ({pipe_radius_m} m)^4) / (8 * ({fluid_viscosity_Pas} Pa·s) * ({pipe_length_m} m))\n\n"
169
+
170
+ f"**Step 5:** Calculate the Final Flow Rate\n"
171
+ f"Performing the calculation gives the volumetric flow rate.\n"
172
+ f"Q = {q_flow_rate:.6f} m^3/s\n\n"
173
+
174
+ f"**Answer:** The volumetric flow rate of {fluid_name} through the pipe is {q_flow_rate:.6f} m^3/s."
175
+ )
176
+
177
+ return question, solution
178
+
179
+
180
+ # Template 3 (Intermediate)
181
+ def template_annulus_flowrate():
182
+ """
183
+ Volumetric Flow Rate in an Annulus Calculation
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
231
+ question = (
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
+ )
239
+
240
+ solution = (
241
+ f"**Step 1:** Identify Given Information\n"
242
+ f"First, we list the known values from the problem statement.\n"
243
+ f"- Fluid: {fluid_name}\n"
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
275
+
276
+
277
+ def main():
278
+ """
279
+ Generate numerous instances of each shell momentum balances and velocity distributions in
280
+ laminar flow template with different random seeds and write the results to a JSONL file.
281
+ """
282
+ import json
283
+ import os
284
+
285
+ # Define the output path (Modify this path according to where you are running the code from)
286
+ output_file = "testset/chemical_engineering/transport_phenomena/shell_momentum_balances.jsonl"
287
+
288
+ # Create the directory if it doesn't exist
289
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
290
+
291
+ # List of template functions with their ID and level
292
+ templates = [
293
+ (template_falling_film_max_velocity, "falling_film_max_velocity", "Easy"),
294
+ (template_hagen_poiseuille_flowrate, "hagen_poiseuille_flowrate", "Easy"),
295
+ (template_annulus_flowrate, "annulus_flowrate", "Intermediate"),
296
+ ]
297
+
298
+ # List to store all generated problems
299
+ all_problems = []
300
+
301
+ # Generate problems for each template
302
+ for template_func, id_name, level in templates:
303
+ for _ in range(50):
304
+ # Generate a unique seed for each problem
305
+ seed = random.randint(1_000_000_000, 4_000_000_000)
306
+ random.seed(seed)
307
+
308
+ # Generate the problem and solution
309
+ question, solution = template_func()
310
+
311
+ # Create a JSON entry
312
+ problem_entry = {
313
+ "seed": seed,
314
+ "branch": "chemical_engineering",
315
+ "domain": "transport_phenomena",
316
+ "area": "shell_momentum_balances",
317
+ "id": id_name,
318
+ "level": level,
319
+ "question": question,
320
+ "solution": solution
321
+ }
322
+
323
+ # Add to the list of problems
324
+ all_problems.append(problem_entry)
325
+
326
+ # Shuffle the problems to mix templates and levels
327
+ random.shuffle(all_problems)
328
+
329
+ # Write all problems to a .jsonl file
330
+ with open(output_file, "w") as file:
331
+ for problem in all_problems:
332
+ file.write(json.dumps(problem))
333
+ file.write("\n")
334
+
335
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
336
+
337
+
338
+ if __name__ == "__main__":
339
+ main()
data/templates/branches/chemical_engineering/transport_phenomena/viscosity_and_momentum_transport.py ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ from data.templates.branches.chemical_engineering.constants import COMMON_LIQUIDS, COMMON_GASES, GAS_MOLECULAR_PARAMS, POWER_LAW_FLUIDS
4
+
5
+
6
+ # Template 1 (Easy)
7
+ def template_newtons_law_shear_stress():
8
+ """
9
+ Shear Stress and Force Calculation for Flow Between Parallel Plates
10
+
11
+ Scenario:
12
+ This template models Couette flow, a classic fluid dynamics problem where a layer
13
+ of fluid is sheared between two parallel plates. One plate is stationary, and
14
+ the other moves at a constant velocity. Assuming a linear velocity profile,
15
+ we use Newton's Law of Viscosity to find the shear stress in the fluid and
16
+ the force required to move the plate.
17
+
18
+ The relevant equations are:
19
+ - Velocity Gradient: dvx/dy = V / Y
20
+ - Shear Stress: tau_yx = mu * (dvx/dy)
21
+ - Force: F = tau_yx * A
22
+
23
+ Returns:
24
+ tuple: A tuple containing:
25
+ - str: A question asking to compute shear stress and force.
26
+ - str: A step-by-step solution showing the calculations.
27
+ """
28
+ # 1. Parameterize the inputs with random values
29
+ fluid_name, (density, viscosity) = random.choice(list(COMMON_LIQUIDS.items()))
30
+ V = round(random.uniform(0.1, 2.0), 2)
31
+ Y_cm = round(random.uniform(0.1, 2.5), 2)
32
+ Y_m = Y_cm / 100
33
+ A = round(random.uniform(0.5, 5.0), 2)
34
+
35
+ # 2. Perform the core calculation
36
+ velocity_gradient = V / Y_m
37
+ shear_stress = viscosity * velocity_gradient
38
+ force = shear_stress * A
39
+
40
+ # 3. Generate the question and solution strings
41
+ question = (
42
+ f"Two large parallel plates with an area of {A} m^2 each are separated by a "
43
+ f"thin film of {fluid_name} that is {Y_cm} cm thick. The top plate is moved "
44
+ f"at a constant velocity of {V} m/s, while the bottom plate is held stationary.\n\n"
45
+ f"Assuming the fluid exhibits Newtonian behavior and a linear velocity profile, calculate:\n"
46
+ f"a) The shear stress (tau_yx) exerted on the fluid.\n"
47
+ f"b) The total force (F) required to move the top plate at the given velocity."
48
+ )
49
+
50
+ solution = (
51
+ f"**Given Information:**\n"
52
+ f"- Fluid: {fluid_name}\n"
53
+ f"- Dynamic Viscosity of {fluid_name} (mu): {viscosity:.2e} Pa·s\n"
54
+ f"- Plate Area (A): {A} m^2\n"
55
+ f"- Plate Velocity (V): {V} m/s\n"
56
+ f"- Distance between plates (Y): {Y_cm} cm = {Y_m} m\n\n"
57
+
58
+ f"**Step 1:** Calculate the velocity gradient (dvx/dy).\n"
59
+ f"For a linear velocity profile between a stationary and a moving plate, the gradient is constant:\n"
60
+ f"dvx/dy = V / Y\n"
61
+ f"dvx/dy = {V} m/s / {Y_m} m = {velocity_gradient:.2f} 1/s\n\n"
62
+
63
+ f"**Step 2:** Calculate the shear stress (tau_yx).\n"
64
+ f"Using Newton's Law of Viscosity:\n"
65
+ f"tau_yx = mu * (dvx/dy)\n"
66
+ f"tau_yx = ({viscosity:.2e} Pa·s) * ({velocity_gradient:.2f} 1/s)\n"
67
+ f"tau_yx = {shear_stress:.3f} Pa\n\n"
68
+
69
+ f"**Step 3:** Calculate the force (F).\n"
70
+ f"Force is the shear stress acting over the entire area of the plate:\n"
71
+ f"F = tau_yx * A\n"
72
+ f"F = ({shear_stress:.3f} Pa) * ({A} m^2)\n"
73
+ f"F = {force:.3f} N\n\n"
74
+
75
+ f"**Answer:**\n"
76
+ f"a) The shear stress in the fluid is **{shear_stress:.3f} Pa**.\n"
77
+ f"b) The force required to move the plate is **{force:.3f} N**."
78
+ )
79
+
80
+ return question, solution
81
+
82
+
83
+ # Template 2 (Easy)
84
+ def template_kinematic_viscosity():
85
+ """
86
+ Kinematic Viscosity Calculation
87
+
88
+ Scenario:
89
+ This template tests the definition of kinematic viscosity (ν), which is the
90
+ ratio of the dynamic viscosity (μ) to the density (ρ) of a fluid. It is a
91
+ measure of a fluid's internal resistance to flow under gravitational forces.
92
+
93
+ The relevant equations are:
94
+ - Kinematic Viscosity: ν = μ / ρ
95
+ - Unit Conversion: 1 m²/s = 10,000 Stokes (St)
96
+
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
+
104
+ # Combine liquids and gases into one dictionary for random selection
105
+ all_fluids = {**COMMON_LIQUIDS, **COMMON_GASES}
106
+ fluid_name, (density, viscosity) = random.choice(list(all_fluids.items()))
107
+
108
+ # Randomly choose the target units for the answer
109
+ target_units = random.choice(["m^2/s", "Stokes (St)"])
110
+
111
+ # 2. Perform the core calculation
112
+
113
+ # Calculate kinematic viscosity in SI units (m²/s)
114
+ nu_si = viscosity / density
115
+
116
+ # Perform unit conversion if necessary
117
+ final_nu = nu_si
118
+ if "Stokes" in target_units:
119
+ final_nu = nu_si * 10000
120
+
121
+ # 3. Generate the question and solution strings
122
+
123
+ question = (
124
+ f"The dynamic viscosity (μ) of {fluid_name} at standard conditions is approximately "
125
+ f"{viscosity:.2e} Pa·s, and its density (ρ) is {density:.3f} kg/m³.\n\n"
126
+ f"Calculate the kinematic viscosity (ν) of {fluid_name} in units of **{target_units}**."
127
+ )
128
+
129
+ solution = (
130
+ f"**Given Information:**\n"
131
+ f"- Fluid: {fluid_name}\n"
132
+ f"- Dynamic Viscosity (μ): {viscosity:.2e} Pa·s\n"
133
+ f"- Density (ρ): {density:.3f} kg/m³\n\n"
134
+
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"
141
+ f"ν = ({viscosity:.2e} Pa·s) / ({density:.3f} kg/m³)\n"
142
+ f"ν = {nu_si:.3e} m²/s\n\n"
143
+ )
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
160
+
161
+
162
+ # Template 3 (Intermediate)
163
+ def template_gas_viscosity_kinetic_theory():
164
+ """
165
+ Gas Viscosity Estimation from Kinetic Theory
166
+
167
+ Scenario:
168
+ This template uses the Chapman-Enskog equation, derived from the kinetic
169
+ theory of gases, to estimate the dynamic viscosity (μ) of a low-density gas.
170
+ This model connects a macroscopic property (viscosity) to molecular
171
+ properties (molar mass, size).
172
+
173
+ The relevant equation is:
174
+ μ = (2.6693e-6 * sqrt(M*T)) / (σ² * Ω_μ)
175
+
176
+ Where:
177
+ - μ = viscosity in Pa·s
178
+ - M = Molar Mass in g/mol
179
+ - T = Absolute Temperature in K
180
+ - σ = Lennard-Jones molecular diameter in Angstroms (Å)
181
+ - Ω_μ = Collision integral (dimensionless)
182
+
183
+ Returns:
184
+ tuple: A tuple containing:
185
+ - str: A question asking to estimate the gas viscosity.
186
+ - str: A step-by-step solution showing the calculation.
187
+ """
188
+ # 1. Parameterize the inputs with random values
189
+ gas_name, (molar_mass, sigma, epsilon) = random.choice(list(GAS_MOLECULAR_PARAMS.items()))
190
+
191
+ # Temperature in Kelvin
192
+ temperature_K = random.randint(250, 600)
193
+
194
+ # Collision integral (dimensionless), kept close to 1.0 for simplicity
195
+ omega_mu = round(random.uniform(0.95, 1.05), 3)
196
+
197
+ # 2. Perform the core calculation
198
+
199
+ # Numerator of the Chapman-Enskog equation
200
+ numerator = 2.6693e-6 * math.sqrt(molar_mass * temperature_K)
201
+
202
+ # Denominator of the Chapman-Enskog equation
203
+ denominator = (sigma**2) * omega_mu
204
+
205
+ # Final viscosity calculation
206
+ viscosity = numerator / denominator
207
+
208
+ # 3. Generate the question and solution strings
209
+
210
+ question = (
211
+ f"Estimate the dynamic viscosity (μ) of {gas_name} gas at a temperature of {temperature_K} K.\n\n"
212
+ f"Use the Chapman-Enskog equation for low-density gases:\n"
213
+ f"μ = (2.6693e-6 * sqrt(M*T)) / (σ^2 * Ω_μ)\n\n"
214
+ f"You are given the following parameters for {gas_name}:\n"
215
+ f"- Molar Mass (M) = {molar_mass} g/mol\n"
216
+ f"- Lennard-Jones diameter (σ) = {sigma} Å\n"
217
+ f"- Collision Integral (Ω_μ) = {omega_mu}\n\n"
218
+ f"Provide your answer in units of Pascal-seconds (Pa·s)."
219
+ )
220
+
221
+ solution = (
222
+ f"**Given Information:**\n"
223
+ f"- Gas: {gas_name}\n"
224
+ f"- Temperature (T): {temperature_K} K\n"
225
+ f"- Molar Mass (M): {molar_mass} g/mol\n"
226
+ f"- Lennard-Jones diameter (σ): {sigma} Å\n"
227
+ f"- Collision Integral (Ω_μ): {omega_mu}\n\n"
228
+
229
+ f"**Step 1:** State the Chapman-Enskog equation.\n"
230
+ f"The formula for estimating the viscosity of a low-density gas is:\n"
231
+ f"μ = (2.6693e-6 * sqrt(M*T)) / (σ^2 * Ω_μ)\n\n"
232
+
233
+ f"**Step 2:** Substitute the given values into the equation.\n"
234
+ f"μ = (2.6693e-6 * sqrt({molar_mass} * {temperature_K})) / (({sigma})^2 * {omega_mu})\n\n"
235
+
236
+ f"**Step 3:** Calculate the numerator and denominator.\n"
237
+ f"- Numerator = 2.6693e-6 * sqrt({molar_mass * temperature_K:.2f}) = {numerator:.4e}\n"
238
+ f"- Denominator = {sigma**2:.3f} * {omega_mu} = {denominator:.3f}\n\n"
239
+
240
+ f"**Step 4:** Calculate the final viscosity.\n"
241
+ f"μ = {numerator:.4e} / {denominator:.3f}\n"
242
+ f"μ = {viscosity:.3e} Pa·s\n\n"
243
+
244
+ f"**Answer:**\n"
245
+ f"The estimated dynamic viscosity of {gas_name} at {temperature_K} K is **{viscosity:.3e} Pa·s**."
246
+ )
247
+
248
+ return question, solution
249
+
250
+
251
+ # Template 4 (Intermediate)
252
+ def template_reynolds_number_flow_regime():
253
+ """
254
+ Reynolds Number and Flow Regime Calculation
255
+
256
+ Scenario:
257
+ This template introduces the Reynolds number (Re), the dimensionless ratio of
258
+ inertial forces to viscous forces in a fluid. Its value is critical for
259
+ predicting whether a flow is smooth (laminar) or chaotic (turbulent).
260
+ The calculation depends on a characteristic length, which varies with geometry.
261
+
262
+ The relevant equation is:
263
+ Re = (ρ * v * L) / μ
264
+
265
+ Where:
266
+ - ρ = density
267
+ - v = average velocity
268
+ - L = characteristic length (e.g., pipe diameter or plate length)
269
+ - μ = dynamic viscosity
270
+
271
+ Returns:
272
+ tuple: A tuple containing:
273
+ - str: A question asking to compute the Reynolds number and find the flow regime.
274
+ - str: A step-by-step solution.
275
+ """
276
+ # 1. Parameterize the inputs with random values
277
+ all_fluids = {**COMMON_LIQUIDS, **COMMON_GASES}
278
+ fluid_name, (density, viscosity) = random.choice(list(all_fluids.items()))
279
+
280
+ # Randomly choose a flow geometry
281
+ geometry = random.choice(['pipe', 'flat plate'])
282
+
283
+ if geometry == 'pipe':
284
+ # Pipe flow parameters
285
+ characteristic_length = round(random.uniform(0.01, 0.5), 3) # Diameter in m
286
+ velocity = round(random.uniform(0.1, 5.0), 2)
287
+ length_symbol = "D"
288
+ length_name = "diameter"
289
+ scenario_description = f"{fluid_name} is flowing through a smooth circular pipe with an internal {length_name} of {characteristic_length} m."
290
+
291
+ else: # flat plate
292
+ # Flat plate flow parameters
293
+ characteristic_length = round(random.uniform(0.1, 2.0), 2) # Length in m
294
+ velocity = round(random.uniform(1.0, 20.0), 1)
295
+ length_symbol = "L"
296
+ length_name = "length"
297
+ scenario_description = f"A flow of {fluid_name} moves over a smooth, thin flat plate with a {length_name} of {characteristic_length} m."
298
+
299
+ # 2. Perform the core calculation
300
+ reynolds_number = (density * velocity * characteristic_length) / viscosity
301
+
302
+ # Determine flow regime based on geometry
303
+ if geometry == 'pipe':
304
+ if reynolds_number < 2300:
305
+ regime = "laminar"
306
+ regime_explanation = f"Since Re = {reynolds_number:,.0f} is less than 2300, the flow is in the **laminar** regime."
307
+ elif reynolds_number < 4000:
308
+ regime = "transitional"
309
+ regime_explanation = f"Since Re = {reynolds_number:,.0f} is between 2300 and 4000, the flow is in the **transitional** regime."
310
+ else:
311
+ regime = "turbulent"
312
+ regime_explanation = f"Since Re = {reynolds_number:,.0f} is greater than 4000, the flow is in the **turbulent** regime."
313
+ else: # flat plate
314
+ if reynolds_number < 500000:
315
+ regime = "laminar"
316
+ regime_explanation = f"Since Re = {reynolds_number:,.0f} is less than the critical value of 500,000, the flow is considered **laminar**."
317
+ else:
318
+ regime = "turbulent"
319
+ regime_explanation = f"Since Re = {reynolds_number:,.0f} is greater than the critical value of 500,000, the flow is considered **turbulent**."
320
+
321
+ # 3. Generate the question and solution strings
322
+ question = (
323
+ f"{scenario_description} The average velocity of the flow is {velocity} m/s.\n\n"
324
+ f"The properties of {fluid_name} are:\n"
325
+ f"- Density (ρ) = {density} kg/m³\n"
326
+ f"- Dynamic Viscosity (μ) = {viscosity:.2e} Pa·s\n\n"
327
+ f"Based on this information:\n"
328
+ f"a) Calculate the Reynolds number (Re).\n"
329
+ f"b) Determine the flow regime (laminar, transitional, or turbulent)."
330
+ )
331
+
332
+ solution = (
333
+ f"**Given Information:**\n"
334
+ f"- Fluid: {fluid_name}\n"
335
+ f"- Density (ρ): {density} kg/m³\n"
336
+ f"- Dynamic Viscosity (μ): {viscosity:.2e} Pa·s\n"
337
+ f"- Average Velocity (v): {velocity} m/s\n"
338
+ f"- Characteristic Length ({length_symbol}): {characteristic_length} m ({length_name} of the {geometry})\n\n"
339
+
340
+ f"**Step 1:** State the formula for the Reynolds number.\n"
341
+ f"The Reynolds number is the ratio of inertial forces to viscous forces.\n"
342
+ f"Re = (ρ * v * {length_symbol}) / μ\n\n"
343
+
344
+ f"**Step 2:** Substitute the values and calculate Re.\n"
345
+ f"Re = ({density} * {velocity} * {characteristic_length}) / {viscosity:.2e}\n"
346
+ f"Re = {reynolds_number:,.0f}\n\n"
347
+
348
+ f"**Step 3:** Determine the flow regime.\n"
349
+ f"For flow in a **{geometry}**, we compare the calculated Re to the standard critical values.\n"
350
+ f"{regime_explanation}\n\n"
351
+
352
+ f"**Answer:**\n"
353
+ f"a) The Reynolds number is approximately **{reynolds_number:,.0f}**.\n"
354
+ f"b) The flow regime is **{regime}**."
355
+ )
356
+
357
+ return question, solution
358
+
359
+
360
+ # Template 5 (Advanced)
361
+ def template_power_law_fluid_shear():
362
+ """
363
+ Shear Stress Calculation for a Power-Law Fluid
364
+
365
+ Scenario:
366
+ This template extends Newton's Law of Viscosity to non-Newtonian fluids
367
+ that follow the Ostwald-de Waele (Power Law) model. Unlike Newtonian fluids,
368
+ their viscosity is dependent on the shear rate. This model is crucial for
369
+ describing common substances like paint, ketchup, and suspensions.
370
+
371
+ The relevant equations are:
372
+ - Shear Stress: τ_yx = K * (dvx/dy)^n
373
+ - Apparent Viscosity: η = K * |dvx/dy|^(n-1)
374
+
375
+ Returns:
376
+ tuple: A tuple containing:
377
+ - str: A question asking for shear stress and apparent viscosity.
378
+ - str: A step-by-step solution.
379
+ """
380
+ # 1. Parameterize the inputs with random values
381
+ fluid_name, (K, n) = random.choice(list(POWER_LAW_FLUIDS.items()))
382
+
383
+ # Velocity of the moving plate in m/s
384
+ V = round(random.uniform(0.1, 2.0), 2)
385
+
386
+ # Distance between plates in cm, converted to meters for calculation
387
+ Y_cm = round(random.uniform(0.5, 5.0), 2)
388
+ Y_m = Y_cm / 100
389
+
390
+ # 2. Perform the core calculation
391
+ velocity_gradient = abs(V / Y_m)
392
+ shear_stress = K * (velocity_gradient ** n)
393
+ apparent_viscosity = K * (velocity_gradient ** (n - 1))
394
+
395
+ # Determine the fluid behavior for the explanation
396
+ if n < 1:
397
+ behavior = f"shear-thinning (pseudoplastic), because its power-law index n ({n}) is less than 1."
398
+ elif n > 1:
399
+ behavior = f"shear-thickening (dilatant), because its power-law index n ({n}) is greater than 1."
400
+ else:
401
+ behavior = "Newtonian, because its power-law index n is exactly 1."
402
+
403
+ # 3. Generate the question and solution strings
404
+ question = (
405
+ f"A non-Newtonian fluid, {fluid_name.lower()}, is placed between two parallel plates "
406
+ f"separated by {Y_cm} cm. The top plate moves at a constant velocity of {V} m/s, "
407
+ f"creating a linear velocity profile in the fluid.\n\n"
408
+ f"The fluid follows the power-law model with the following parameters:\n"
409
+ f"- Consistency Index (K) = {K} Pa·s^n\n"
410
+ f"- Power-Law Index (n) = {n}\n\n"
411
+ f"Calculate:\n"
412
+ f"a) The shear stress (τ_yx) on the fluid.\n"
413
+ f"b) The apparent viscosity (η) at this shear rate."
414
+ )
415
+
416
+ solution = (
417
+ f"**Given Information:**\n"
418
+ f"- Fluid: {fluid_name}\n"
419
+ f"- Consistency Index (K): {K} Pa·s^n\n"
420
+ f"- Power-Law Index (n): {n}\n"
421
+ f"- Plate Velocity (V): {V} m/s\n"
422
+ f"- Plate Separation (Y): {Y_cm} cm = {Y_m} m\n\n"
423
+
424
+ f"**Step 1:** Characterize the Fluid Behavior.\n"
425
+ f"The fluid is {behavior} This means its apparent viscosity will change with the rate of shear.\n\n"
426
+
427
+ f"**Step 2:** Calculate the Velocity Gradient (Shear Rate).\n"
428
+ f"Assuming a linear velocity profile:\n"
429
+ f"Shear Rate (dvx/dy) = V / Y = {V} m/s / {Y_m} m = {velocity_gradient:.2f} 1/s\n\n"
430
+
431
+ f"**Step 3:** Calculate the Shear Stress (τ_yx).\n"
432
+ f"Using the power-law formula: τ_yx = K * (dvx/dy)^n\n"
433
+ f"τ_yx = {K} * ({velocity_gradient:.2f})^{n}\n"
434
+ f"τ_yx = {shear_stress:.3f} Pa\n\n"
435
+
436
+ f"**Step 4:** Calculate the Apparent Viscosity (η).\n"
437
+ f"Apparent viscosity is the effective viscosity at a specific shear rate.\n"
438
+ f"η = K * |dvx/dy|^(n-1)\n"
439
+ f"η = {K} * ({velocity_gradient:.2f})^({n - 1})\n"
440
+ f"η = {apparent_viscosity:.4f} Pa·s\n\n"
441
+
442
+ f"**Answer:**\n"
443
+ f"a) The shear stress on the fluid is **{shear_stress:.3f} Pa**.\n"
444
+ f"b) The apparent viscosity at this shear rate is **{apparent_viscosity:.4f} Pa·s**."
445
+ )
446
+
447
+ return question, solution
448
+
449
+
450
+ def main():
451
+ """
452
+ Generate numerous instances of each viscosity and mechanisms of momentum transport template
453
+ with different random seeds and write the results to a JSONL file.
454
+ """
455
+ import json
456
+ import os
457
+
458
+ # Define the output path (Modify this path according to where you are running the code from)
459
+ output_file = "testset/chemical_engineering/transport_phenomena/viscosity_and_momentum_transport.jsonl"
460
+
461
+ # Create the directory if it doesn't exist
462
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
463
+
464
+ # List of template functions with their ID and level
465
+ templates = [
466
+ (template_newtons_law_shear_stress, "newtons_law_shear_stress", "Easy"),
467
+ (template_kinematic_viscosity, "kinematic_viscosity", "Easy"),
468
+ (template_gas_viscosity_kinetic_theory, "gas_viscosity_kinetic_theory", "Intermediate"),
469
+ (template_reynolds_number_flow_regime, "reynolds_number_flow_regime", "Intermediate"),
470
+ (template_power_law_fluid_shear, "power_law_fluid_shear", "Advanced"),
471
+ ]
472
+
473
+ # List to store all generated problems
474
+ all_problems = []
475
+
476
+ # Generate problems for each template
477
+ for template_func, id_name, level in templates:
478
+ for _ in range(50):
479
+ # Generate a unique seed for each problem
480
+ seed = random.randint(1_000_000_000, 4_000_000_000)
481
+ random.seed(seed)
482
+
483
+ # Generate the problem and solution
484
+ question, solution = template_func()
485
+
486
+ # Create a JSON entry
487
+ problem_entry = {
488
+ "seed": seed,
489
+ "branch": "chemical_engineering",
490
+ "domain": "transport_phenomena",
491
+ "area": "viscosity_and_momentum_transport",
492
+ "id": id_name,
493
+ "level": level,
494
+ "question": question,
495
+ "solution": solution
496
+ }
497
+
498
+ # Add to the list of problems
499
+ all_problems.append(problem_entry)
500
+
501
+ # Shuffle the problems to mix templates and levels
502
+ random.shuffle(all_problems)
503
+
504
+ # Write all problems to a .jsonl file
505
+ with open(output_file, "w") as file:
506
+ for problem in all_problems:
507
+ file.write(json.dumps(problem))
508
+ file.write("\n")
509
+
510
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
511
+
512
+
513
+ if __name__ == "__main__":
514
+ main()
data/templates/branches/electrical_engineering/__pycache__/constants.cpython-311.pyc ADDED
Binary file (1.91 kB). View file
 
data/templates/branches/electrical_engineering/constants.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+
3
+ # Speed of light in vacuum (m/s)
4
+ C0 = 299792458
5
+
6
+ # Dictionary of common media and their approximate phase velocities for EM waves
7
+ MEDIA_VELOCITIES = {
8
+ # Gases (at 0°C and 1 atm, for visible light ~589 nm)
9
+ "Vacuum": C0,
10
+ "Air (at sea level)": C0 / 1.000293,
11
+ "Helium": C0 / 1.000036,
12
+ "Carbon Dioxide": C0 / 1.00045,
13
+
14
+ # Liquids (for visible light ~589 nm)
15
+ "Water (distilled, 20°C)": C0 / 1.333,
16
+ "Ethanol": C0 / 1.36,
17
+ "Glycerine": C0 / 1.473,
18
+ "Benzene": C0 / 1.501,
19
+ "Carbon Disulfide": C0 / 1.628, # notable for high dispersion
20
+
21
+ # Solids (for visible light ~589 nm)
22
+ "Ice": C0 / 1.31,
23
+ "Teflon (PTFE)": C0 / 1.35,
24
+ "Fused Silica (Glass)": C0 / 1.458,
25
+ "Crown Glass (typical)": C0 / 1.52,
26
+ "Polyethylene": C0 / 1.54,
27
+ "Polystyrene": C0 / 1.59,
28
+ "Flint Glass (dense)": C0 / 1.65,
29
+ "Sapphire": C0 / 1.77,
30
+ "Glass (amorphous semiconductor)": C0 / 1.8,
31
+ "Diamond": C0 / 2.42,
32
+ "Gallium Phosphide (GaP)": C0 / 3.5,
33
+
34
+ # Special Cases (Important for RF/Microwave Engineering)
35
+ "Human Body Tissue (muscle, ~3 GHz)": C0 / 7.14, # Relative permittivity ε_r ~51, n=√ε_r
36
+ }
37
+
38
+ # Permittivity of free space in Farads per meter (F/m)
39
+ EPSILON_0 = 8.854e-12
40
+
41
+ # Value ranges for random parameter generation to ensure diverse problems.
42
+ # Frequencies are kept as integers for clarity in the problem statement.
43
+ FREQUENCY_RANGE_HZ = (50, 2000)
44
+ AMPLITUDE_RANGE = (1.0, 50.0)
45
+ PHASE_RANGE_DEG = (-180, 180)
46
+ PHASE_RANGE_RAD = (-math.pi, math.pi)
47
+
48
+ # The continuous frequency Omega will be a multiple of pi. This range defines the multiplier.
49
+ OMEGA_MULTIPLIER_RANGE = (100, 1000)
50
+
51
+ SAMPLING_FREQ_RANGE_HZ = (1000, 8000)
52
+
53
+ F0_RANGE_HZ = (500, 3000)
54
+
55
+ # The gain of the discrete-time system
56
+ GAIN_K_RANGE = (0.5, 5.0)
57
+
58
+ # The delay (in samples) of the discrete-time system
59
+ DELAY_N0_RANGE = (1, 10)
60
+
61
+
62
+ # The integer factor by which the signal is downsampled.
63
+ DECIMATION_FACTOR_M_RANGE = (2, 5)
64
+
65
+ # Define the pool for denominators of the omega_0 fraction.
66
+ # Using larger numbers allows for more granularity in creating frequencies.
67
+ OMEGA_DENOMINATOR_RANGE = (8, 20)
data/templates/branches/electrical_engineering/digital_communications/__pycache__/deterministic_and_random_signal_analysis.cpython-311.pyc ADDED
Binary file (29.2 kB). View file
 
data/templates/branches/electrical_engineering/digital_communications/__pycache__/digital_modulation_schemes.cpython-311.pyc ADDED
Binary file (28.2 kB). View file
 
data/templates/branches/electrical_engineering/digital_communications/deterministic_and_random_signal_analysis.py ADDED
@@ -0,0 +1,570 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+
4
+
5
+ # Template 1 (Easy)
6
+ def template_signal_energy_power():
7
+ """
8
+ Signal Energy and Power Calculation
9
+
10
+ Scenario:
11
+ This template tests the ability to classify a signal as either an energy
12
+ signal (finite energy) or a power signal (finite average power) and to
13
+ calculate the corresponding value. It covers time-limited, time-unlimited
14
+ decaying, and periodic signals.
15
+
16
+ Core Equations:
17
+ Signal Energy: Eg = integral from -inf to inf of |g(t)|^2 dt
18
+ Signal Power: Pg = lim(T->inf) (1/T) * integral from -T/2 to T/2 of |g(t)|^2 dt
19
+
20
+ Returns:
21
+ tuple: A tuple containing:
22
+ - str: A question asking to classify and calculate the energy/power of a signal.
23
+ - str: A step-by-step solution.
24
+ """
25
+ # 1. Parameterize the inputs with random values
26
+ signal_type = random.choice(['rect_pulse', 'exp_decay', 'periodic'])
27
+ amplitude = random.randint(2, 20)
28
+ precision = 2
29
+
30
+ # Common text for all questions to ensure unit clarity
31
+ unit_context = "Assume the signal g(t) represents a voltage in Volts (V) across a 1-ohm resistor."
32
+
33
+ if signal_type == 'rect_pulse':
34
+ # --- Energy Signal: Rectangular Pulse ---
35
+ duration = random.randint(2, 10)
36
+ start_time = 0
37
+ end_time = start_time + duration
38
+ signal_expr = f"g(t) = {amplitude} for {start_time} <= t <= {end_time}, and 0 otherwise"
39
+ energy = (amplitude ** 2) * duration
40
+
41
+ question = (
42
+ f"Consider the signal defined as:\n"
43
+ f"{signal_expr}\n\n"
44
+ f"{unit_context}\n\n"
45
+ f"Is this an energy signal or a power signal? Calculate its total energy or "
46
+ f"average power accordingly."
47
+ )
48
+
49
+ solution = (
50
+ f"**Given:**\n"
51
+ f"The signal is {signal_expr}.\n\n"
52
+
53
+ f"**Step 1:** Classify the signal.\n"
54
+ f"The signal is non-zero only for a finite duration ({duration} seconds). "
55
+ f"Time-limited signals have finite energy and zero average power. "
56
+ f"Therefore, this is an **energy signal**.\n\n"
57
+
58
+ f"**Step 2:** State the formula for signal energy.\n"
59
+ f"The energy (Eg) of a signal g(t) is:\n"
60
+ f"Eg = integral from -inf to inf of |g(t)|^2 dt\n\n"
61
+
62
+ f"**Step 3:** Apply the formula to the given signal.\n"
63
+ f"Since the signal is non-zero only between t = {start_time} and t = {end_time}, the integral becomes:\n"
64
+ f"Eg = integral from {start_time} to {end_time} of ({amplitude})^2 dt = integral from {start_time} to {end_time} of {amplitude**2} dt\n\n"
65
+
66
+ f"**Step 4:** Calculate the integral.\n"
67
+ f"Eg = {amplitude**2} * [t] (from t={start_time} to {end_time})\n"
68
+ f"Eg = {amplitude**2} * ({end_time} - {start_time}) = {amplitude**2} * {duration} = {energy}\n\n"
69
+
70
+ f"**Answer:**\n"
71
+ f"The signal is an **energy signal** with a total energy of **{round(energy, precision)} Joules (V^2 * s)**."
72
+ )
73
+
74
+ elif signal_type == 'exp_decay':
75
+ # --- Energy Signal: Exponential Decay ---
76
+ alpha = random.randint(2, 10)
77
+ signal_expr = f"g(t) = {amplitude} * exp(-{alpha}*t) * u(t)"
78
+ energy = (amplitude ** 2) / (2 * alpha)
79
+
80
+ question = (
81
+ f"Consider the signal defined as:\n"
82
+ f"{signal_expr}\n"
83
+ f"where u(t) is the unit step function.\n\n"
84
+ f"{unit_context}\n\n"
85
+ f"Is this an energy signal or a power signal? Calculate its total energy or "
86
+ f"average power accordingly."
87
+ )
88
+
89
+ solution = (
90
+ f"**Given:**\n"
91
+ f"The signal is {signal_expr}.\n\n"
92
+
93
+ f"**Step 1:** Classify the signal.\n"
94
+ f"The signal is not time-limited, but it decays to zero as t approaches infinity. "
95
+ f"This means its total energy is finite. Therefore, this is an **energy signal**.\n\n"
96
+
97
+ f"**Step 2:** State the formula for signal energy.\n"
98
+ f"Eg = integral from -inf to inf of |g(t)|^2 dt\n\n"
99
+
100
+ f"**Step 3:** Apply the formula to the given signal.\n"
101
+ f"Due to the unit step function u(t), the integral's limits become 0 to infinity:\n"
102
+ f"Eg = integral from 0 to inf of ({amplitude} * exp(-{alpha}*t))^2 dt\n"
103
+ f"Eg = {amplitude**2} * integral from 0 to inf of exp(-{2*alpha}*t) dt\n\n"
104
+
105
+ f"**Step 4:** Calculate the integral.\n"
106
+ f"Eg = {amplitude**2} * [-1/{2*alpha} * exp(-{2*alpha}*t)] (from t=0 to inf)\n"
107
+ f"Eg = {amplitude**2} * ( (0) - (-1/{2*alpha} * exp(0)) ) = {amplitude**2} * (1/{2*alpha})\n"
108
+ f"Eg = {amplitude**2} / {2*alpha} = {energy}\n\n"
109
+
110
+ f"**Answer:**\n"
111
+ f"The signal is an **energy signal** with a total energy of **{round(energy, precision)} Joules (V^2 * s)**."
112
+ )
113
+
114
+ else: # signal_type == 'periodic'
115
+ # --- Power Signal: Periodic Sinusoid ---
116
+ frequency = random.randint(10, 100)
117
+ signal_expr = f"g(t) = {amplitude} * cos(2 * pi * {frequency} * t)"
118
+ power = (amplitude ** 2) / 2
119
+
120
+ question = (
121
+ f"Consider the signal defined as:\n"
122
+ f"{signal_expr}\n\n"
123
+ f"{unit_context}\n\n"
124
+ f"Is this an energy signal or a power signal? Calculate its total energy or "
125
+ f"average power accordingly."
126
+ )
127
+
128
+ solution = (
129
+ f"**Given:**\n"
130
+ f"The signal is {signal_expr}.\n\n"
131
+
132
+ f"**Step 1:** Classify the signal.\n"
133
+ f"The signal is periodic and continues for all time without decaying. Its total energy would be infinite. "
134
+ f"However, its average power over time is finite and non-zero. "
135
+ f"Therefore, this is a **power signal**.\n\n"
136
+
137
+ f"**Step 2:** State the formula for signal power.\n"
138
+ f"For a periodic signal with period T0, the average power (Pg) is calculated over one period:\n"
139
+ f"Pg = (1/T0) * integral over T0 of |g(t)|^2 dt\n\n"
140
+
141
+ f"**Step 3:** Apply the formula to the given signal.\n"
142
+ f"For any sinusoidal signal of the form A*cos(w*t + phi), the average power is a standard result:\n"
143
+ f"Pg = A^2 / 2\n"
144
+ f"This result is derived from integrating (A * cos(...))^2 over one full period.\n\n"
145
+
146
+ f"**Step 4:** Calculate the power.\n"
147
+ f"In this case, the amplitude A is {amplitude}.\n"
148
+ f"Pg = ({amplitude})^2 / 2\n"
149
+ f"Pg = {amplitude**2} / 2 = {power}\n\n"
150
+
151
+ f"**Answer:**\n"
152
+ f"The signal is a **power signal** with an average power of **{round(power, precision)} Watts (V^2)**."
153
+ )
154
+
155
+ return question, solution
156
+
157
+
158
+ # Template 2 (Easy)
159
+ def template_mean_variance():
160
+ """
161
+ Mean and Variance of a Random Variable
162
+
163
+ Scenario:
164
+ This template assesses the understanding of basic statistical properties of a
165
+ discrete random variable, specifically its central tendency (mean) and
166
+ dispersion (variance).
167
+
168
+ Core Equations:
169
+ Mean (Expected Value): mu_X = E[X] = sum(x_i * P(x_i))
170
+ Variance: sigma_X^2 = E[(X - mu_X)^2] = sum((x_i - mu_X)^2 * P(x_i))
171
+
172
+ Returns:
173
+ tuple: A tuple containing:
174
+ - str: A question asking for the mean and variance of a discrete random variable.
175
+ - str: A step-by-step solution.
176
+ """
177
+ # 1. Parameterize the inputs with random values
178
+ num_values = random.randint(3, 5)
179
+ precision = 3
180
+
181
+ # Generate a set of unique, sorted integer values for the random variable
182
+ values = sorted(random.sample(range(-10, 21), num_values))
183
+
184
+ # Generate clean, rational probabilities that sum to 1
185
+ # Create random integer parts, sum them, and use that sum as the denominator
186
+ parts = [random.randint(1, 10) for _ in range(num_values)]
187
+ total_sum = sum(parts)
188
+ probabilities = [p / total_sum for p in parts]
189
+
190
+ # 2. Perform the core calculations
191
+ # Calculate the mean (expected value)
192
+ mean = sum(v * p for v, p in zip(values, probabilities))
193
+
194
+ # Calculate the variance
195
+ variance = sum(((v - mean) ** 2) * p for v, p in zip(values, probabilities))
196
+
197
+ # 3. Generate the question and solution strings
198
+
199
+ # Format values and probabilities for display
200
+ values_str = "{" + ", ".join(map(str, values)) + "}"
201
+ probs_str = "{" + ", ".join([f"{p:.{precision}f}" for p in probabilities]) + "}"
202
+
203
+ question = (
204
+ f"A discrete random variable X can take the values {values_str} with "
205
+ f"corresponding probabilities {probs_str}, respectively.\n\n"
206
+ f"Calculate the mean (mu_X) and variance (sigma_X^2) of X."
207
+ )
208
+
209
+ # --- Build the solution string step-by-step ---
210
+
211
+ # Mean calculation steps
212
+ mean_calc_lhs = "mu_X = E[X]"
213
+ mean_calc_rhs = " + ".join([f"({v})({p:.{precision}f})" for v, p in zip(values, probabilities)])
214
+ mean_interm_vals = " + ".join([f"{v * p:.{precision}f}" for v, p in zip(values, probabilities)])
215
+
216
+ # Variance calculation steps
217
+ var_calc_lhs = "sigma_X^2 = E[(X - mu_X)^2]"
218
+ var_calc_rhs = " + ".join(
219
+ [f"({v} - {mean:.{precision}f})^2({p:.{precision}f})" for v, p in zip(values, probabilities)]
220
+ )
221
+ var_interm_vals = " + ".join(
222
+ [f"({(v - mean):.{precision}f})^2({p:.{precision}f})" for v, p in zip(values, probabilities)]
223
+ )
224
+ var_final_vals = " + ".join(
225
+ [f"{((v - mean)**2 * p):.{precision}f}" for v, p in zip(values, probabilities)]
226
+ )
227
+
228
+ solution = (
229
+ f"**Given:**\n"
230
+ f"Values: X = {values_str}\n"
231
+ f"Probabilities: P(X) = {probs_str}\n\n"
232
+
233
+ f"**Step 1:** Calculate the Mean (mu_X).\n"
234
+ f"The formula for the mean (expected value) is:\n"
235
+ f"mu_X = sum(x_i * P(x_i))\n\n"
236
+ f"Plugging in the given values:\n"
237
+ f"{mean_calc_lhs} = {mean_calc_rhs}\n"
238
+ f"{mean_calc_lhs} = {mean_interm_vals}\n"
239
+ f"{mean_calc_lhs} = {mean:.{precision}f}\n\n"
240
+
241
+ f"**Step 2:** Calculate the Variance (sigma_X^2).\n"
242
+ f"The formula for variance is:\n"
243
+ f"sigma_X^2 = sum((x_i - mu_X)^2 * P(x_i))\n\n"
244
+ f"Using the calculated mean (mu_X = {mean:.{precision}f}):\n"
245
+ f"{var_calc_lhs} = {var_calc_rhs}\n"
246
+ f"{var_calc_lhs} = {var_interm_vals}\n"
247
+ f"{var_calc_lhs} = {var_final_vals}\n"
248
+ f"{var_calc_lhs} = {variance:.{precision}f}\n\n"
249
+
250
+ f"**Answer:**\n"
251
+ f"The mean of the random variable X is **{mean:.{precision}f}**.\n"
252
+ f"The variance of the random variable X is **{variance:.{precision}f}**."
253
+ )
254
+
255
+ return question, solution
256
+
257
+
258
+ # Template 3 (Intermediate)
259
+ def template_autocorrelation_rect_pulse():
260
+ """
261
+ Autocorrelation of a Deterministic Signal
262
+
263
+ Scenario:
264
+ This template tests the ability to calculate the autocorrelation function of
265
+ a simple deterministic signal, a rectangular pulse. This measures the
266
+ similarity of the signal with a time-shifted version of itself and
267
+ introduces the concept of deriving a new function shape (a triangle) from
268
+ this operation.
269
+
270
+ Core Equation:
271
+ Autocorrelation: R_g(tau) = integral from -inf to inf of g(t) * g(t - tau) dt
272
+
273
+ Returns:
274
+ tuple: A tuple containing:
275
+ - str: A question asking for the autocorrelation of a rectangular pulse.
276
+ - str: A step-by-step solution.
277
+ """
278
+ # 1. Parameterize the inputs with random values
279
+ amplitude = random.randint(2, 10)
280
+
281
+ # Use a half-duration to ensure the total duration is an even number
282
+ # and the pulse limits are clean integers.
283
+ half_duration = random.randint(1, 5)
284
+ duration = 2 * half_duration
285
+
286
+ # 2. Perform the core calculation
287
+ # The autocorrelation of a rectangular pulse is a triangular function.
288
+ # Peak value R(0) = A^2 * T
289
+ peak_value = (amplitude ** 2) * duration
290
+
291
+ # The resulting function is R(tau) = A^2 * (T - |tau|) for |tau| <= T
292
+ result_function_str = f"{amplitude**2}*({duration} - |tau|)"
293
+
294
+ # 3. Generate the question and solution strings
295
+ signal_expr = f"g(t) = {amplitude} for -{half_duration} <= t <= {half_duration}, and 0 otherwise"
296
+
297
+ question = (
298
+ f"Find the autocorrelation function R_g(tau) for the rectangular pulse signal defined as:\n"
299
+ f" {signal_expr}"
300
+ )
301
+
302
+ solution = (
303
+ f"**Given:**\n"
304
+ f"The signal is a rectangular pulse: {signal_expr}.\n"
305
+ f"It has an amplitude A = {amplitude} and a total duration T = {duration}.\n\n"
306
+
307
+ f"**Step 1:** Understand the Autocorrelation Integral.\n"
308
+ f"The autocorrelation function is defined as R_g(tau) = integral of g(t) * g(t - tau) dt.\n"
309
+ f"Conceptually, this measures the overlapping area between the signal g(t) and a version of itself shifted by 'tau'.\n\n"
310
+
311
+ f"**Step 2:** Analyze the Overlap of the Pulses.\n"
312
+ f"The original pulse g(t) exists from t = -{half_duration} to t = {half_duration}.\n"
313
+ f"The shifted pulse g(t - tau) exists from t = tau - {half_duration} to t = tau + {half_duration}.\n"
314
+ f"The product g(t) * g(t - tau) is non-zero only where these two pulses overlap.\n"
315
+ f"In the overlapping region, the value of the product is ({amplitude}) * ({amplitude}) = {amplitude**2}.\n"
316
+ f"The pulses only overlap when the shift 'tau' is between -{duration} and {duration}. Outside this range, the autocorrelation is zero.\n\n"
317
+
318
+ f"**Step 3:** Determine the Area of the Overlap.\n"
319
+ f"The integral is simply the area of the rectangular overlapping region.\n"
320
+ f"Area = (Height of Overlap) * (Width of Overlap)\n\n"
321
+ f"The height is constant at {amplitude**2}.\n"
322
+ f"The width (duration) of the overlap depends on the shift 'tau'. When tau = 0, the overlap is the entire pulse duration, {duration}. As |tau| increases, the overlap width decreases linearly. The width is given by the expression: ({duration} - |tau|).\n\n"
323
+
324
+ f"**Step 4:** Formulate the Autocorrelation Function.\n"
325
+ f"Combining the height and width, the area of overlap is:\n"
326
+ f"R_g(tau) = {amplitude**2} * ({duration} - |tau|)\n\n"
327
+ f"This equation is valid for the range where overlap occurs, which is |tau| <= {duration}. For |tau| > {duration}, R_g(tau) = 0.\n\n"
328
+
329
+ f"**Answer:**\n"
330
+ f"The autocorrelation function is a **triangular function** described by:\n"
331
+ f"**R_g(tau) = {result_function_str}**, for **|tau| <= {duration}**, and **0** otherwise.\n"
332
+ f"This triangle has a peak value of **{peak_value}** at tau = 0."
333
+ )
334
+
335
+ return question, solution
336
+
337
+
338
+ # Template 4 (Intermediate)
339
+ def template_ft_esd_rect_pulse():
340
+ """
341
+ Fourier Transform and Energy Spectral Density (ESD)
342
+
343
+ Scenario:
344
+ This template requires finding the Fourier Transform of a simple energy
345
+ signal (a rectangular pulse) and then calculating its Energy Spectral
346
+ Density (ESD). It demonstrates the fundamental relationship between a
347
+ signal's time-domain representation and its frequency-domain energy distribution.
348
+
349
+ Core Equations:
350
+ Fourier Transform: G(f) = integral from -inf to inf of g(t)*exp(-j*2*pi*f*t) dt
351
+ Energy Spectral Density (ESD): Psi_g(f) = |G(f)|^2
352
+
353
+ Returns:
354
+ tuple: A tuple containing:
355
+ - str: A question asking for the FT and ESD of a rectangular pulse.
356
+ - str: A step-by-step solution.
357
+ """
358
+ # 1. Parameterize the inputs with random values
359
+ amplitude = random.randint(5, 20)
360
+ duration = random.choice([0.5, 1.0, 1.5, 2.0, 4.0])
361
+ half_duration = duration / 2
362
+
363
+ # 2. Perform the core calculation and format results
364
+ ft_amplitude = amplitude * duration
365
+ esd_amplitude = ft_amplitude ** 2
366
+
367
+ # Handle sinc argument formatting for cleaner output
368
+ if duration == 1.0:
369
+ sinc_arg_str = "f"
370
+ else:
371
+ sinc_arg_str = f"{duration}*f"
372
+
373
+ ft_result_str = f"{ft_amplitude} * sinc({sinc_arg_str})"
374
+ esd_result_str = f"{esd_amplitude} * sinc^2({sinc_arg_str})"
375
+
376
+ # 3. Generate the question and solution strings
377
+ signal_expr = f"g(t) with amplitude {amplitude} extending from t = -{half_duration} s to t = {half_duration} s"
378
+
379
+ question = (
380
+ f"Given a rectangular pulse {signal_expr}, find its Fourier Transform G(f) and "
381
+ f"its Energy Spectral Density Psi_g(f).\n\n"
382
+ f"Note: Use the definition sinc(x) = sin(pi*x) / (pi*x)."
383
+ )
384
+
385
+ solution = (
386
+ f"**Given:**\n"
387
+ f"A rectangular pulse g(t) with:\n"
388
+ f"Amplitude (A) = {amplitude}\n"
389
+ f"Total Duration (T) = {duration} s (from -{half_duration} to {half_duration})\n\n"
390
+
391
+ f"**Step 1:** Identify the Fourier Transform Pair for a Rectangular Pulse.\n"
392
+ f"A rectangular pulse centered at the origin, g(t) = A for |t| <= T/2, has a well-known Fourier Transform which is a sinc function:\n"
393
+ f"G(f) = A * T * sinc(f * T)\n"
394
+ f"where sinc(x) is defined as sin(pi*x) / (pi*x).\n\n"
395
+
396
+ f"**Step 2:** Calculate the Fourier Transform G(f).\n"
397
+ f"We plug in our given values A = {amplitude} and T = {duration} into the formula:\n"
398
+ f"G(f) = ({amplitude}) * ({duration}) * sinc(f * {duration})\n"
399
+ f"G(f) = {ft_result_str}\n\n"
400
+
401
+ f"**Step 3:** State the Formula for Energy Spectral Density (ESD).\n"
402
+ f"The ESD, denoted Psi_g(f), describes how the energy of the signal is distributed over frequency. It is calculated as the squared magnitude of the Fourier Transform:\n"
403
+ f"Psi_g(f) = |G(f)|^2\n\n"
404
+
405
+ f"**Step 4:** Calculate the Energy Spectral Density Psi_g(f).\n"
406
+ f"We take the magnitude squared of the G(f) we found in Step 2. Since our G(f) is real-valued, this is equivalent to just squaring the function:\n"
407
+ f"Psi_g(f) = |{ft_result_str}|^2\n"
408
+ f"Psi_g(f) = ({amplitude} * {duration})^2 * sinc^2({sinc_arg_str})\n"
409
+ f"Psi_g(f) = {esd_result_str}\n\n"
410
+
411
+ f"**Answer:**\n"
412
+ f"The Fourier Transform is **G(f) = {ft_result_str}**.\n"
413
+ f"The Energy Spectral Density is **Psi_g(f) = {esd_result_str}**."
414
+ )
415
+
416
+ return question, solution
417
+
418
+
419
+ # Template 5 (Advanced)
420
+ def template_lowpass_equivalent_bandpass():
421
+ """
422
+ Lowpass Equivalent of a Bandpass Signal
423
+
424
+ Scenario:
425
+ A fundamental concept in communications is representing a high-frequency bandpass
426
+ signal using a lower-frequency complex equivalent. This template tests the
427
+ ability to derive this lowpass equivalent signal from a given bandpass
428
+ signal by expanding it into its canonical in-phase and quadrature components.
429
+
430
+ Core Equations:
431
+ Bandpass Signal: g(t) = g_I(t)*cos(2*pi*fc*t) - g_Q(t)*sin(2*pi*fc*t)
432
+ Lowpass Equivalent Signal: g_tilde(t) = g_I(t) + j*g_Q(t)
433
+ Trigonometric Identity: cos(a + b) = cos(a)*cos(b) - sin(a)*sin(b)
434
+
435
+ Returns:
436
+ tuple: A tuple containing:
437
+ - str: A question asking for the components of a bandpass signal.
438
+ - str: A step-by-step solution showing the derivation.
439
+ """
440
+ # 1. Parameterize the inputs with random values
441
+ amplitude = random.randint(10, 100)
442
+ fc_exp = random.randint(6, 8) # For MHz to hundreds of MHz
443
+ fc_mult = random.randint(1, 9)
444
+ phase_deg = random.choice([30, 45, 60, 90, 120, 135, 150])
445
+ precision = 2
446
+
447
+ # Create a readable string for the carrier frequency
448
+ fc_str = f"{fc_mult} x 10^{fc_exp}"
449
+
450
+ # 2. Perform the core calculations
451
+ phase_rad = math.radians(phase_deg)
452
+ cos_phi = math.cos(phase_rad)
453
+ sin_phi = math.sin(phase_rad)
454
+
455
+ # From the identity, g(t) = A*cos(phi)*cos(w_c*t) - A*sin(phi)*sin(w_c*t)
456
+ # We can identify the in-phase and quadrature components
457
+ g_I = amplitude * cos_phi
458
+ g_Q = amplitude * sin_phi
459
+
460
+ # 3. Generate the question and solution strings
461
+ signal_expr = f"g(t) = {amplitude} * cos(2*pi*({fc_str})*t + {phase_deg} deg)"
462
+
463
+ question = (
464
+ f"A bandpass signal is described by the following equation:\n"
465
+ f"{signal_expr}\n\n"
466
+ f"Express this signal in its canonical form to determine its in-phase component (g_I(t)), "
467
+ f"quadrature component (g_Q(t)), and its complex lowpass equivalent signal (g_tilde(t))."
468
+ )
469
+
470
+ solution = (
471
+ f"**Given:**\n"
472
+ f"The bandpass signal is {signal_expr}.\n\n"
473
+
474
+ f"**Step 1:** State the Goal and the Required Identity.\n"
475
+ f"Our goal is to match the given signal to the canonical bandpass form:\n"
476
+ f"g(t) = g_I(t)*cos(2*pi*fc*t) - g_Q(t)*sin(2*pi*fc*t)\n"
477
+ f"To do this, we use the trigonometric angle addition identity:\n"
478
+ f"cos(a + b) = cos(a)*cos(b) - sin(a)*sin(b)\n\n"
479
+
480
+ f"**Step 2:** Expand the Given Signal Expression.\n"
481
+ f"Let a = 2*pi*fc*t and b = {phase_deg} deg. Applying the identity to g(t):\n"
482
+ f"g(t) = {amplitude} * [cos(2*pi*fc*t)*cos({phase_deg} deg) - sin(2*pi*fc*t)*sin({phase_deg} deg)]\n"
483
+ f"g(t) = ({amplitude}*cos({phase_deg} deg))*cos(2*pi*fc*t) - ({amplitude}*sin({phase_deg} deg))*sin(2*pi*fc*t)\n\n"
484
+
485
+ f"**Step 3:** Identify the In-Phase (g_I) and Quadrature (g_Q) Components.\n"
486
+ f"By comparing our expanded signal to the canonical form, we can identify g_I(t) and g_Q(t):\n"
487
+ f"g_I(t) is the term multiplying cos(2*pi*fc*t).\n"
488
+ f"g_Q(t) is the term multiplying sin(2*pi*fc*t) (note the minus sign is part of the formula).\n\n"
489
+ f"g_I(t) = {amplitude} * cos({phase_deg} deg) = {amplitude} * ({cos_phi:.{precision+1}f}) = {g_I:.{precision+1}f}\n"
490
+ f"g_Q(t) = {amplitude} * sin({phase_deg} deg) = {amplitude} * ({sin_phi:.{precision+1}f}) = {g_Q:.{precision+1}f}\n"
491
+ f"Note that for this signal, g_I and g_Q are constants because the amplitude and phase are constant.\n\n"
492
+
493
+ f"**Step 4:** Construct the Complex Lowpass Equivalent Signal (g_tilde(t)).\n"
494
+ f"The complex lowpass equivalent is defined as g_tilde(t) = g_I(t) + j*g_Q(t).\n"
495
+ f"g_tilde(t) = {round(g_I, precision)} + j*{round(g_Q, precision)}\n\n"
496
+
497
+ f"**Answer:**\n"
498
+ f"In-Phase Component: **g_I(t) = {round(g_I, precision)}**\n"
499
+ f"Quadrature Component: **g_Q(t) = {round(g_Q, precision)}**\n"
500
+ f"Complex Lowpass Equivalent: **g_tilde(t) = {round(g_I, precision)} + j*{round(g_Q, precision)}**"
501
+ )
502
+
503
+ return question, solution
504
+
505
+
506
+ def main():
507
+ """
508
+ Generate numerous instances of each deterministic and random signal analysis template
509
+ with different random seeds and write the results to a JSONL file.
510
+ """
511
+ import json
512
+ import os
513
+
514
+ # Define the output path (Modify this path according to where you are running the code from)
515
+ output_file = "testset/electrical_engineering/digital_communications/deterministic_and_random_signal_analysis.jsonl"
516
+
517
+ # Create the directory if it doesn't exist
518
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
519
+
520
+ # List of template functions with their ID and level
521
+ templates = [
522
+ (template_signal_energy_power, "signal_energy_power", "Easy"),
523
+ (template_mean_variance, "mean_variance_discrete_rv", "Easy"),
524
+ (template_autocorrelation_rect_pulse, "autocorrelation_rect_pulse", "Intermediate"),
525
+ (template_ft_esd_rect_pulse, "ft_esd_rect_pulse", "Intermediate"),
526
+ (template_lowpass_equivalent_bandpass, "lowpass_equivalent_bandpass", "Advanced"),
527
+ ]
528
+
529
+ # List to store all generated problems
530
+ all_problems = []
531
+
532
+ # Generate problems for each template
533
+ for template_func, id_name, level in templates:
534
+ for _ in range(50):
535
+ # Generate a unique seed for each problem
536
+ seed = random.randint(1_000_000_000, 4_000_000_000)
537
+ random.seed(seed)
538
+
539
+ # Generate the problem and solution
540
+ question, solution = template_func()
541
+
542
+ # Create a JSON entry
543
+ problem_entry = {
544
+ "seed": seed,
545
+ "branch": "electrical_engineering",
546
+ "domain": "digital_communications",
547
+ "area": "deterministic_and_random_signal_analysis",
548
+ "id": id_name,
549
+ "level": level,
550
+ "question": question,
551
+ "solution": solution
552
+ }
553
+
554
+ # Add to the list of problems
555
+ all_problems.append(problem_entry)
556
+
557
+ # Shuffle the problems to mix templates and levels
558
+ random.shuffle(all_problems)
559
+
560
+ # Write all problems to a .jsonl file
561
+ with open(output_file, "w") as file:
562
+ for problem in all_problems:
563
+ file.write(json.dumps(problem))
564
+ file.write("\n")
565
+
566
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
567
+
568
+
569
+ if __name__ == "__main__":
570
+ main()
data/templates/branches/electrical_engineering/digital_communications/digital_modulation_schemes.py ADDED
@@ -0,0 +1,664 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+
4
+
5
+ # Template 1 (Easy)
6
+ def template_bpsk_energy_basis():
7
+ """
8
+ BPSK/BASK Signal Energy and Basis Function
9
+
10
+ Scenario:
11
+ This template tests the understanding of how to analyze a basic modulated
12
+ signal, which is a fundamental first step in signal space analysis. It
13
+ involves calculating the energy of the signal (Eb) over one bit period
14
+ and deriving its corresponding orthonormal basis function (psi(t)). These
15
+ two components are essential for representing signals as vectors.
16
+
17
+ Core Equations:
18
+ Signal: s(t) = A * cos(2*pi*fc*t)
19
+ Energy Integral: Eb = integral from 0 to Tb of [s(t)]^2 dt
20
+ Simplified Energy: Eb = (A^2 * Tb) / 2
21
+ Basis Function: psi_1(t) = s(t) / sqrt(Eb) = sqrt(2 / Tb) * cos(2*pi*fc*t)
22
+
23
+ Returns:
24
+ tuple: A tuple containing:
25
+ - str: A question about BPSK signal properties.
26
+ - str: A step-by-step solution.
27
+ """
28
+ # 1. Parameterize the inputs with random values
29
+
30
+ # Set a standard precision for rounding in all calculations and outputs
31
+ precision = 2
32
+
33
+ amplitude = round(random.uniform(1.0, 10.0), precision)
34
+
35
+ # Generate bit duration in a range from 1 microsecond to 10 milliseconds
36
+ bit_duration_s = random.uniform(1e-6, 1e-2)
37
+
38
+ # Choose a carrier frequency that is an integer multiple of the bit rate (1/Tb)
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
+
59
+ # Calculate energy per bit: Eb = (A^2 * Tb) / 2
60
+ energy_joules = (amplitude**2 * bit_duration_s) / 2
61
+
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
+
74
+ question = (
75
+ f"A binary ASK (BASK) signal is defined by the following equation for one bit interval:\n"
76
+ f"s(t) = {amplitude} * cos(2*pi*{carrier_freq_str}*t)\n"
77
+ f"for the time interval 0 <= t <= {bit_duration_str}.\n\n"
78
+ f"Calculate the following:\n"
79
+ f"a) The energy per bit (Eb).\n"
80
+ f"b) The orthonormal basis function psi_1(t)."
81
+ )
82
+
83
+ solution = (
84
+ f"**Given Signal:**\n"
85
+ f"s(t) = {amplitude} * cos(2*pi*{carrier_freq_str}*t) for 0 <= t <= {bit_duration_str}\n\n"
86
+
87
+ f"**Step 1:** Identify Parameters\n"
88
+ f"From the signal definition, we can extract the following parameters:\n"
89
+ f"Amplitude (A): {amplitude} V\n"
90
+ f"Bit Duration (Tb): {bit_duration_str} ({bit_duration_s:.4e} s)\n"
91
+ f"Carrier Frequency (fc): {carrier_freq_str} ({carrier_freq_hz:.4e} Hz)\n\n"
92
+
93
+ f"**Step 2:** Calculate Energy per Bit (Eb)\n"
94
+ f"The energy of a signal is the integral of its squared magnitude over its duration.\n"
95
+ f"Eb = integral from 0 to Tb of [s(t)]^2 dt\n"
96
+ f"For a sinusoidal signal of the form A*cos(...), the energy over an interval Tb simplifies to:\n"
97
+ f"Eb = (A^2 * Tb) / 2\n"
98
+ f"Substituting the values:\n"
99
+ f"Eb = ({amplitude}^2 * {bit_duration_s:.4e}) / 2\n"
100
+ f"Eb = {energy_joules:.4e} J\n"
101
+ f"Eb = {energy_str}\n\n"
102
+
103
+ f"**Step 3:** Determine the Basis Function (psi_1(t))\n"
104
+ f"The orthonormal basis function is found by normalizing the signal by the square root of its energy.\n"
105
+ f"psi_1(t) = s(t) / sqrt(Eb)\n"
106
+ f"We can use the general formula derived from this relationship:\n"
107
+ f"psi_1(t) = sqrt(2 / Tb) * cos(2*pi*fc*t)\n"
108
+ f"First, calculate the amplitude of the basis function:\n"
109
+ f"Amplitude_psi = sqrt(2 / {bit_duration_s:.4e} s) = {round(basis_amplitude, precision)}\n"
110
+ f"Now, construct the full basis function:\n"
111
+ f"psi_1(t) = {round(basis_amplitude, precision)} * cos(2*pi*{carrier_freq_str}*t)\n\n"
112
+
113
+ f"**Answer:**\n"
114
+ f"a) The energy per bit (Eb) is {energy_str}.\n"
115
+ f"b) The basis function (psi_1(t)) is {round(basis_amplitude, precision)} * cos(2*pi*{carrier_freq_str}*t)."
116
+ )
117
+
118
+ return question, solution
119
+
120
+
121
+ # Template 2 (Intermediate)
122
+ def template_euclidean_distance_binary():
123
+ """
124
+ Euclidean Distance for Binary Modulation
125
+
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:
147
+ - str: A question about Euclidean distance.
148
+ - str: A step-by-step solution.
149
+ """
150
+ # 1. Parameterize the inputs with random values
151
+
152
+ precision = 3
153
+ modulation_type = random.choice(['BPSK', 'Orthogonal BFSK'])
154
+
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
198
+ # BFSK signals are orthogonal (90 degrees apart)
199
+ constellation_type = "orthogonal"
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
222
+
223
+ question = (
224
+ f"A digital communication system uses {modulation_type} modulation.\n"
225
+ f"The energy per bit is Eb = {energy_str}.\n\n"
226
+ f"Determine the following:\n"
227
+ f"a) The signal constellation points (s1 and s2) in vector form.\n"
228
+ f"b) The Euclidean distance (d) between these two points."
229
+ )
230
+
231
+ solution = (
232
+ f"**Given Information:**\n"
233
+ f"Modulation Scheme: {modulation_type}\n"
234
+ f"Energy per Bit (Eb): {energy_str} ({energy_joules:.{precision}e} J)\n\n"
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
249
+
250
+
251
+ # Template 3 (Intermediate)
252
+ def template_average_energy_mqam():
253
+ """
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
+
293
+ # Build the solution steps string based on the value of M
294
+ if M == 4:
295
+ coordinate_set = "{+/-A}"
296
+ solution_steps = (
297
+ f"**Step 2:** List Signal Point Energies\n"
298
+ f"For 4-QAM, there is only one type of signal point, located at coordinates (+/-A, +/-A).\n"
299
+ f"There are 4 points, all with energy E = A^2 + A^2 = 2A^2.\n\n"
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 * (2 * A^2) = 8 * A^2\n"
304
+ f"E_avg = (Total Energy) / M = (8 * A^2) / 4 = 2 * A^2\n"
305
+ )
306
+
307
+ elif M == 16:
308
+ coordinate_set = "{+/-A, +/-3A}"
309
+ solution_steps = (
310
+ f"**Step 2:** List Signal Point Energies\n"
311
+ f"For 16-QAM, the points can be grouped by their distance from the origin (their energy).\n"
312
+ f"4 points at (+/-A, +/-A) have energy E1 = A^2 + A^2 = 2A^2.\n"
313
+ f"8 points at (+/-A, +/-3A) or (+/-3A, +/-A) have energy E2 = A^2 + (3A)^2 = 10A^2.\n"
314
+ f"4 points at (+/-3A, +/-3A) have energy E3 = (3A)^2 + (3A)^2 = 18A^2.\n\n"
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 = (
342
+ f"For a square {M}-QAM constellation, the signal points are located at (xi, yj) where the coordinates xi and yj are chosen from the set {coordinate_set}.\n\n"
343
+ f"The fundamental distance parameter is A = {A}.\n\n"
344
+ f"Calculate the average energy per symbol (E_avg) for this constellation."
345
+ )
346
+
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"
354
+
355
+ f"{solution_steps}"
356
+
357
+ f"**Step 4:** Final Calculation\n"
358
+ f"Now, we substitute the value of A = {A}.\n"
359
+ f"E_avg = {round(avg_energy_coeff, precision)} * ({A})^2\n"
360
+ f"E_avg = {round(avg_energy_coeff, precision)} * {round(A**2, precision)}\n"
361
+ f"E_avg = {round(avg_energy, precision)}\n\n"
362
+
363
+ f"**Answer:**\n"
364
+ f"The average energy per symbol is {round(avg_energy, precision)}."
365
+ )
366
+
367
+ return question, solution
368
+
369
+
370
+ # Template 4 (Advanced)
371
+ def template_ber_estimation_mary():
372
+ """
373
+ Bit Error Rate (BER) Estimation for M-ary Schemes
374
+
375
+ Scenario:
376
+ Predicting the performance of a digital communication system in the presence of
377
+ noise is a critical engineering task. This template tests the ability to
378
+ estimate the bit error rate (BER) from a given signal-to-noise ratio per
379
+ bit (Eb/N0) for common M-ary modulation schemes. It requires using standard
380
+ approximation formulas that are expressed in terms of the complementary
381
+ Gaussian distribution function, Q(x).
382
+
383
+ Core Equations:
384
+ Linear SNR: (Eb/N0)_lin = 10^((Eb/N0)_dB / 10)
385
+ Bits per symbol: k = log2(M)
386
+ M-PSK SER: Ps approx 2 * Q(sqrt(2 * Es/N0) * sin(pi/M))
387
+ M-QAM SER: Ps approx 4 * (1 - 1/sqrt(M)) * Q(sqrt(3*k/(M-1) * Eb/N0))
388
+ Gray Code BER: Pb approx Ps / k
389
+
390
+ Returns:
391
+ tuple: A tuple containing:
392
+ - str: A question asking for the BER estimation.
393
+ - str: A step-by-step solution showing the calculation.
394
+ """
395
+ # 1. Enhanced Parameter Randomization
396
+ precision = random.randint(1, 2)
397
+ modulation_scheme = random.choice(['M-PSK', 'M-QAM'])
398
+
399
+ if modulation_scheme == 'M-PSK':
400
+ M = random.choice([4, 8, 16, 32])
401
+ else: # M-QAM
402
+ # 4-QAM is identical to 4-PSK (QPSK), so we start at 16 for variety.
403
+ M = random.choice([16, 64, 256])
404
+
405
+ eb_n0_db = round(random.uniform(5.0, 25.0), precision)
406
+
407
+ # 2. Perform the core calculation
408
+ eb_n0_lin = 10**(eb_n0_db / 10)
409
+ k = math.log2(M)
410
+
411
+ # Logic varies based on modulation scheme
412
+ if modulation_scheme == 'M-PSK':
413
+ # For M-PSK, the formula uses Es/N0
414
+ es_n0_lin = k * eb_n0_lin
415
+ q_arg = math.sqrt(2 * es_n0_lin) * math.sin(math.pi / M)
416
+ ser_coeff = 2
417
+ ber_coeff = ser_coeff / k
418
+
419
+ ser_approx_str = f"{ser_coeff} * Q({q_arg:.{precision+1}f})"
420
+ ber_approx_str = f"{ber_coeff:.{precision+1}f} * Q({q_arg:.{precision+1}f})"
421
+
422
+ calculation_steps = (
423
+ f"**Step 2:** Calculate Symbol SNR (Es/N0)\n"
424
+ f"The number of bits per symbol is k = log2({M}) = {k:.0f}.\n"
425
+ f"The energy per symbol (Es) is k times the energy per bit (Eb).\n"
426
+ f"Es/N0 = k * (Eb/N0) = {k:.0f} * {eb_n0_lin:.{precision}f} = {es_n0_lin:.{precision}f}\n\n"
427
+
428
+ f"**Step 3:** Calculate Symbol Error Rate (SER)\n"
429
+ f"For M-PSK, the SER is approximated by: Ps approx 2*Q(sqrt(2*Es/N0)*sin(pi/M)).\n"
430
+ f"First, calculate the argument of the Q-function:\n"
431
+ f"arg = sqrt(2 * {es_n0_lin:.{precision}f}) * sin(pi / {M})\n"
432
+ f"arg = {q_arg:.{precision+1}f}\n"
433
+ f"So, the SER is: Ps approx {ser_approx_str}\n"
434
+ )
435
+
436
+ else: # M-QAM
437
+ # For square M-QAM, the formula directly uses Eb/N0
438
+ q_arg = math.sqrt((3 * k / (M - 1)) * eb_n0_lin)
439
+ ser_coeff = 4 * (1 - 1/math.sqrt(M))
440
+ ber_coeff = ser_coeff / k
441
+
442
+ ser_approx_str = f"{ser_coeff:.{precision+1}f} * Q({q_arg:.{precision+1}f})"
443
+ ber_approx_str = f"{ber_coeff:.{precision+1}f} * Q({q_arg:.{precision+1}f})"
444
+
445
+ calculation_steps = (
446
+ f"**Step 2:** Calculate Bits per Symbol (k)\n"
447
+ f"The number of bits per symbol is k = log2({M}) = {k:.0f}.\n\n"
448
+
449
+ f"**Step 3:** Calculate Symbol Error Rate (SER)\n"
450
+ f"For square M-QAM, the SER is approximated by: Ps approx 4*(1-1/sqrt(M))*Q(sqrt(3*k/(M-1) * Eb/N0)).\n"
451
+ f"First, calculate the argument of the Q-function:\n"
452
+ f"arg = sqrt( (3*{k:.0f} / ({M}-1)) * {eb_n0_lin:.{precision}f} )\n"
453
+ f"arg = {q_arg:.{precision+1}f}\n"
454
+ f"So, the SER is: Ps approx {ser_approx_str}\n"
455
+ )
456
+
457
+ # 3. Generate the question and solution strings
458
+ question = (
459
+ f"A digital communication system uses {M}-{modulation_scheme.split('-')[1]} modulation over an AWGN channel.\n"
460
+ f"The system operates with a signal-to-noise ratio per bit of Eb/N0 = {eb_n0_db} dB.\n\n"
461
+ "Assuming a Gray code mapping is used, provide a mathematical expression for the estimated Bit Error Rate (BER)."
462
+ )
463
+
464
+ solution = (
465
+ f"**Given Information:**\n"
466
+ f"Modulation: {M}-{modulation_scheme.split('-')[1]}\n"
467
+ f"Eb/N0: {eb_n0_db} dB\n\n"
468
+
469
+ f"**Step 1:** Convert SNR from dB to Linear Scale\n"
470
+ f"The linear value of Eb/N0 is required for the error rate formulas.\n"
471
+ f"Eb/N0 (linear) = 10^( ({eb_n0_db}) / 10 ) = {eb_n0_lin:.{precision}f}\n\n"
472
+
473
+ f"{calculation_steps}\n"
474
+
475
+ f"**Step 4:** Approximate Bit Error Rate (BER)\n"
476
+ f"For Gray-coded constellations, the BER can be approximated from the SER by Pb approx Ps / k.\n"
477
+ f"Pb approx ({ser_approx_str}) / {k:.0f}\n"
478
+ f"Pb approx {ber_approx_str}\n\n"
479
+
480
+ f"**Answer:**\n"
481
+ f"The estimated Bit Error Rate (BER) is expressed as:\n"
482
+ f"BER approx {ber_approx_str}"
483
+ )
484
+
485
+ return question, solution
486
+
487
+
488
+ # Template 5 (Advanced)
489
+ def template_null_to_null_bandwidth():
490
+ """
491
+ Null-to-Null Bandwidth Calculation
492
+
493
+ Scenario:
494
+ Understanding the bandwidth requirements of a signal is essential for designing
495
+ and analyzing communication systems. The null-to-null bandwidth of the main
496
+ spectral lobe is a fundamental, albeit theoretical, measure of the spectrum
497
+ occupied by a signal shaped with basic rectangular pulses. This template tests
498
+ the ability to calculate this bandwidth by relating the user data rate (Rb)
499
+ to the transmission symbol rate (Rs) for various M-ary modulation schemes.
500
+
501
+ Core Equations:
502
+ Bits per Symbol: k = log2(M)
503
+ Symbol Rate: Rs = Rb / k
504
+ Null-to-Null RF Bandwidth: B_null = 2 * Rs
505
+
506
+ Returns:
507
+ tuple: A tuple containing:
508
+ - str: A question asking for the null-to-null bandwidth.
509
+ - str: A step-by-step solution showing the calculation.
510
+ """
511
+ # 1. Enhanced Parameter Randomization
512
+ precision = 2
513
+ modulation_scheme = random.choice(['M-PSK', 'M-QAM', 'M-PAM'])
514
+
515
+ # Choose M from a scheme-appropriate set
516
+ if modulation_scheme == 'M-PSK':
517
+ M = random.choice([2, 4, 8, 16, 32, 64, 128])
518
+ elif modulation_scheme == 'M-QAM':
519
+ M = random.choice([4, 16, 32, 64, 128, 256, 512, 1024])
520
+ else: # M-PAM
521
+ M = random.choice([2, 4, 8, 16, 32, 64])
522
+
523
+ # Randomize bit rate value and unit for diversity
524
+ unit_choice = random.choice(['kbps', 'Mbps', 'Gbps'])
525
+ if unit_choice == 'kbps':
526
+ value = random.randint(100, 999)
527
+ multiplier = 1e3
528
+ elif unit_choice == 'Mbps':
529
+ value = random.choice([1, 1.5, 2, 5, 10, 25, 50, 100])
530
+ multiplier = 1e6
531
+ else: # Gbps
532
+ value = random.choice([1, 2.5, 10])
533
+ multiplier = 1e9
534
+
535
+ bit_rate_str = f"{value} {unit_choice}"
536
+ bit_rate_hz = value * multiplier
537
+
538
+ # 2. Perform the core calculation
539
+ k = math.log2(M)
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)
547
+ prefix_b = prefixes.get(exponent_b, '')
548
+ scaled_b = bandwidth_hz / (10**exponent_b)
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, '')
558
+ scaled_rs = symbol_rate_rs / (10**exponent_rs)
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
566
+ question = (
567
+ f"A digital communication system transmits data at a rate of {bit_rate_str} using {M}-{modulation_scheme.split('-')[1]} modulation.\n\n"
568
+ "Assuming the signal is shaped with rectangular baseband pulses, what is the null-to-null RF bandwidth of the transmitted signal's main spectral lobe?"
569
+ )
570
+
571
+ solution = (
572
+ f"**Given Information:**\n"
573
+ f"Modulation: {M}-{modulation_scheme.split('-')[1]}\n"
574
+ f"Bit Rate (Rb): {bit_rate_str} ({bit_rate_hz:.2e} bps)\n\n"
575
+
576
+ f"**Step 1:** Calculate Bits per Symbol (k)\n"
577
+ f"This value determines how many bits are grouped together to form a single symbol.\n"
578
+ f"k = log2(M) = log2({M}) = {k:.0f} bits/symbol\n\n"
579
+
580
+ f"**Step 2:** Calculate Symbol Rate (Rs)\n"
581
+ f"The symbol rate (or baud rate) is the rate at which symbols are transmitted. It is found by dividing the bit rate by the number of bits per symbol.\n"
582
+ f"Rs = Rb / k\n"
583
+ f"Rs = {bit_rate_hz:.2e} bps / {k:.0f} bits/symbol\n"
584
+ f"Rs = {symbol_rate_rs:.2e} symbols/s = {symbol_rate_str}\n\n"
585
+
586
+ f"**Step 3:** Calculate Null-to-Null Bandwidth (B_null)\n"
587
+ f"For a signal using rectangular pulses of duration Ts, the baseband spectrum is a sinc function with its first null at frequency 1/Ts. The symbol rate Rs is equal to 1/Ts.\n"
588
+ f"For a passband RF signal, the main lobe bandwidth is twice the baseband null frequency.\n"
589
+ f"B_null = 2 * (1/Ts) = 2 * Rs\n"
590
+ f"B_null = 2 * {symbol_rate_rs:.2e} symbols/s\n"
591
+ f"B_null = {bandwidth_hz:.2e} Hz = {bandwidth_str}\n\n"
592
+
593
+ f"**Answer:**\n"
594
+ f"The null-to-null RF bandwidth is {bandwidth_str}."
595
+ )
596
+
597
+ return question, solution
598
+
599
+
600
+ def main():
601
+ """
602
+ Generate numerous instances of each digital modulation schemes template
603
+ with different random seeds and write the results to a JSONL file.
604
+ """
605
+ import json
606
+ import os
607
+
608
+ # Define the output path (Modify this path according to where you are running the code from)
609
+ output_file = "testset/electrical_engineering/digital_communications/digital_modulation_schemes.jsonl"
610
+
611
+ # Create the directory if it doesn't exist
612
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
613
+
614
+ # List of template functions with their ID and level
615
+ templates = [
616
+ (template_bpsk_energy_basis, "bpsk_energy_basis", "Easy"),
617
+ (template_euclidean_distance_binary, "euclidean_distance_binary", "Intermediate"),
618
+ (template_average_energy_mqam, "average_energy_mqam", "Intermediate"),
619
+ (template_ber_estimation_mary, "ber_estimation_mary", "Advanced"),
620
+ (template_null_to_null_bandwidth, "null_to_null_bandwidth", "Advanced"),
621
+ ]
622
+
623
+ # List to store all generated problems
624
+ all_problems = []
625
+
626
+ # Generate problems for each template
627
+ for template_func, id_name, level in templates:
628
+ for _ in range(50):
629
+ # Generate a unique seed for each problem
630
+ seed = random.randint(1_000_000_000, 4_000_000_000)
631
+ random.seed(seed)
632
+
633
+ # Generate the problem and solution
634
+ question, solution = template_func()
635
+
636
+ # Create a JSON entry
637
+ problem_entry = {
638
+ "seed": seed,
639
+ "branch": "electrical_engineering",
640
+ "domain": "digital_communications",
641
+ "area": "digital_modulation_schemes",
642
+ "id": id_name,
643
+ "level": level,
644
+ "question": question,
645
+ "solution": solution
646
+ }
647
+
648
+ # Add to the list of problems
649
+ all_problems.append(problem_entry)
650
+
651
+ # Shuffle the problems to mix templates and levels
652
+ random.shuffle(all_problems)
653
+
654
+ # Write all problems to a .jsonl file
655
+ with open(output_file, "w") as file:
656
+ for problem in all_problems:
657
+ file.write(json.dumps(problem))
658
+ file.write("\n")
659
+
660
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
661
+
662
+
663
+ if __name__ == "__main__":
664
+ main()
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/electrostatics.cpython-311.pyc ADDED
Binary file (30.1 kB). View file
 
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/magnetostatics.cpython-311.pyc ADDED
Binary file (9.53 kB). View file
 
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/waves_and_phasors.cpython-311.pyc ADDED
Binary file (33.8 kB). View file
 
data/templates/branches/electrical_engineering/electromagnetics_and_waves/electrostatics.py ADDED
@@ -0,0 +1,566 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ import numpy as np
4
+ from data.templates.branches.electrical_engineering.constants import EPSILON_0
5
+
6
+
7
+ # Template 1 (Easy)
8
+ def template_coulombs_law():
9
+ """
10
+ Coulomb's Law for Two Point Charges
11
+
12
+ Scenario:
13
+ This template tests the fundamental calculation of the electrostatic force
14
+ between two point charges in a dielectric medium. It requires finding the
15
+ separation vector between the charges and applying the vector form of
16
+ Coulomb's Law.
17
+
18
+ Core Equations:
19
+ R_vec = P2 - P1
20
+ F_vec = (1 / (4 * pi * epsilon_0 * epsilon_r)) * (q1 * q2 / |R_vec|^3) * R_vec
21
+
22
+ Returns:
23
+ tuple: A tuple containing:
24
+ - str: A question asking for the force vector between two charges.
25
+ - str: A step-by-step solution showing the calculation.
26
+ """
27
+ # 1. Parameterize the inputs with random values
28
+
29
+ # Generate random integer charges between -15 and 15 uC (excluding 0)
30
+ q1_val = random.choice([i for i in range(-15, 16) if i != 0])
31
+ q2_val = random.choice([i for i in range(-15, 16) if i != 0])
32
+
33
+ # Convert from microcoulombs to coulombs for calculation
34
+ q1_calc = q1_val * 1e-6
35
+ q2_calc = q2_val * 1e-6
36
+
37
+ # Generate two unique 3D points with integer coordinates from -5 to 5
38
+ p1 = np.array([random.randint(-5, 5) for _ in range(3)])
39
+ p2 = np.array([random.randint(-5, 5) for _ in range(3)])
40
+ while np.array_equal(p1, p2):
41
+ p2 = np.array([random.randint(-5, 5) for _ in range(3)])
42
+
43
+ # Choose a relative permittivity for the medium
44
+ epsilon_r = round(random.uniform(1.0, 5.0), 2)
45
+
46
+ # Standardize precision for final outputs
47
+ precision = 3
48
+
49
+ # 2. Perform the core calculation
50
+
51
+ # Calculate the separation vector and its magnitude
52
+ R_vec = p2 - p1
53
+ R_mag = np.linalg.norm(R_vec)
54
+
55
+ # Calculate the full permittivity of the medium
56
+ epsilon = epsilon_r * EPSILON_0
57
+
58
+ # Calculate the scalar part of Coulomb's Law
59
+ scalar_coefficient = (q1_calc * q2_calc) / (4 * math.pi * epsilon * (R_mag**3))
60
+
61
+ # Calculate the final force vector
62
+ F_vec = scalar_coefficient * R_vec
63
+
64
+ # 3. Generate the question and solution strings
65
+
66
+ question = (
67
+ f"Two point charges are located in a medium with a relative permittivity of {epsilon_r}.\n"
68
+ f"Charge q1 = {q1_val} uC is at position P1 = {p1} m.\n"
69
+ f"Charge q2 = {q2_val} uC is at position P2 = {p2} m.\n\n"
70
+ f"Determine the electrostatic force vector F12 exerted by charge q1 on charge q2."
71
+ )
72
+
73
+ solution = (
74
+ f"**Given:**\n"
75
+ f" - Charge q1 = {q1_val} uC = {q1_val}e-6 C at P1 = {p1} m\n"
76
+ f" - Charge q2 = {q2_val} uC = {q2_val}e-6 C at P2 = {p2} m\n"
77
+ f" - Relative permittivity epsilon_r = {epsilon_r}\n\n"
78
+ f"**Constants:**\n"
79
+ f" - Permittivity of free space, epsilon_0 = {EPSILON_0:.4e} F/m\n\n"
80
+
81
+ f"**Step 1:** Find the separation vector from q1 to q2.\n"
82
+ f" The vector R12 points from P1 to P2.\n"
83
+ f" R12 = P2 - P1 = {p2} - {p1} = {R_vec} m.\n\n"
84
+
85
+ f"**Step 2:** Calculate the magnitude of the separation vector.\n"
86
+ f" |R12| = sqrt({R_vec[0]}^2 + {R_vec[1]}^2 + {R_vec[2]}^2)\n"
87
+ f" |R12| = sqrt({R_vec[0]**2} + {R_vec[1]**2} + {R_vec[2]**2}) = sqrt({R_mag**2:.2f}) = {R_mag:.{precision}f} m.\n\n"
88
+
89
+ f"**Step 3:** Apply the vector form of Coulomb's Law.\n"
90
+ f" The formula for the force F12 is:\n"
91
+ f" F12 = (1 / (4 * pi * epsilon)) * (q1 * q2 / |R12|^3) * R12\n"
92
+ f" where epsilon = epsilon_r * epsilon_0.\n\n"
93
+
94
+ f" First, calculate the total permittivity:\n"
95
+ f" epsilon = {epsilon_r} * {EPSILON_0:.4e} = {epsilon:.4e} F/m.\n\n"
96
+
97
+ f" Now, substitute all values into the formula:\n"
98
+ f" F12 = (1 / (4 * pi * {epsilon:.4e})) * (({q1_calc:.2e}) * ({q2_calc:.2e}) / ({R_mag:.{precision}f})^3) * {R_vec}\n"
99
+ f" F12 = ({1/(4 * math.pi * epsilon):.3e}) * ({(q1_calc * q2_calc):.3e} / {R_mag**3:.3e}) * {R_vec}\n"
100
+ f" F12 = ({scalar_coefficient:.3e}) * {R_vec}\n"
101
+ f" F12 = <{F_vec[0]:.{precision}e}, {F_vec[1]:.{precision}e}, {F_vec[2]:.{precision}e}> N.\n\n"
102
+
103
+ f"**Answer:**\n"
104
+ f" The electrostatic force vector exerted by q1 on q2 is "
105
+ f"<{F_vec[0]:.{precision}e}, {F_vec[1]:.{precision}e}, {F_vec[2]:.{precision}e}> N."
106
+ )
107
+
108
+ return question, solution
109
+
110
+
111
+ # Template 2 (Intermediate)
112
+ def template_superposition_electric_field():
113
+ """
114
+ Superposition of Electric Fields (Intermediate)
115
+
116
+ Scenario:
117
+ This template requires calculating the net electric field at a target
118
+ point due to two or three discrete point charges. It builds on the
119
+ basic E-field calculation by applying the principle of superposition,
120
+ which involves vector addition. The problem is constrained to 2D
121
+ to keep the vector calculations straightforward.
122
+
123
+ Core Equations:
124
+ E_i = (1 / (4 * pi * epsilon_0)) * (q_i / |R_i|^3) * R_i
125
+ E_net = E_1 + E_2 + ...
126
+
127
+ Returns:
128
+ tuple: A tuple containing:
129
+ - str: A question asking for the net electric field at a point.
130
+ - str: A step-by-step solution showing the calculation for each charge
131
+ and the final vector sum.
132
+ """
133
+ # 1. Parameterize the inputs with random values
134
+
135
+ num_charges = random.choice([2, 3])
136
+ charges_val = []
137
+ positions = []
138
+
139
+ # Use a set to ensure all points (charges and target) are unique
140
+ used_points = set()
141
+
142
+ for i in range(num_charges):
143
+ # Generate random integer charges between -25 and 25 nC (excluding 0)
144
+ q_val = random.choice([i for i in range(-25, 26) if i != 0])
145
+ charges_val.append(q_val)
146
+
147
+ # Generate a unique 2D point for each charge
148
+ while True:
149
+ pos = tuple(random.randint(-10, 10) for _ in range(2))
150
+ if pos not in used_points:
151
+ positions.append(np.array(pos))
152
+ used_points.add(pos)
153
+ break
154
+
155
+ # Generate a unique 2D target point
156
+ while True:
157
+ target_pos = tuple(random.randint(-10, 10) for _ in range(2))
158
+ if target_pos not in used_points:
159
+ P_target = np.array(target_pos)
160
+ used_points.add(target_pos)
161
+ break
162
+
163
+ # Convert from nanocoulombs to coulombs for calculation
164
+ charges_calc = [q * 1e-9 for q in charges_val]
165
+
166
+ # Standardize precision for final outputs
167
+ precision = 2
168
+
169
+ # 2. Perform the core calculation
170
+
171
+ E_net = np.array([0.0, 0.0])
172
+ solution_steps = ""
173
+
174
+ for i in range(num_charges):
175
+ q_calc = charges_calc[i]
176
+ q_val = charges_val[i]
177
+ pos = positions[i]
178
+
179
+ # Calculate separation vector and its magnitude
180
+ R_vec = P_target - pos
181
+ R_mag = np.linalg.norm(R_vec)
182
+
183
+ # Calculate the E-field vector from this charge
184
+ scalar_part = q_calc / (4 * math.pi * EPSILON_0 * R_mag**3)
185
+ E_vec = scalar_part * R_vec
186
+
187
+ # Add to the net field
188
+ E_net += E_vec
189
+
190
+ # Build the solution string for this step
191
+ solution_steps += (
192
+ f"**Step {i+1}:** Calculate the Electric Field from q{i+1} ({q_val} nC).\n"
193
+ f" The separation vector from q{i+1} to the target point P is:\n"
194
+ f" R{i+1} = P - P{i+1} = {P_target} - {pos} = {R_vec} m.\n"
195
+ f" The magnitude is |R{i+1}| = sqrt({R_vec[0]**2} + {R_vec[1]**2}) = {R_mag:.{precision+1}f} m.\n"
196
+ f" The electric field E{i+1} is given by E = (k * q / |R|^3) * R, where k = 1/(4*pi*eps0).\n"
197
+ f" E{i+1} = ({1/(4*math.pi*EPSILON_0):.2e}) * ({q_calc:.2e} / {R_mag:.{precision+1}f}^3) * {R_vec}\n"
198
+ f" E{i+1} = <{E_vec[0]:.{precision}e}, {E_vec[1]:.{precision}e}> N/C.\n\n"
199
+ )
200
+
201
+ # 3. Generate the question and solution strings
202
+
203
+ charge_descriptions = ""
204
+ for i in range(num_charges):
205
+ charge_descriptions += f" - Charge q{i+1} = {charges_val[i]} nC is at position P{i+1} = {positions[i]} m.\n"
206
+
207
+ question = (
208
+ f"Several point charges are located in a vacuum on a 2D plane:\n"
209
+ f"{charge_descriptions}"
210
+ f"An observation point is located at P = {P_target} m.\n\n"
211
+ f"Using the principle of superposition, determine the net electric field vector E_net at point P."
212
+ )
213
+
214
+ solution = (
215
+ f"**Given:**\n"
216
+ f"{charge_descriptions}"
217
+ f" - Observation point P = {P_target} m.\n"
218
+ f" - The medium is a vacuum (epsilon_r = 1.0).\n\n"
219
+ f"**Principle of Superposition:**\n"
220
+ f" The total electric field at a point is the vector sum of the electric fields produced by each individual charge.\n"
221
+ f" E_net = E1 + E2{' + E3' if num_charges == 3 else ''}\n\n"
222
+
223
+ f"{solution_steps}"
224
+ f"**Step {num_charges+1}:** Sum the vectors to find the net electric field.\n"
225
+ f" E_net = E1 + E2{' + E3' if num_charges == 3 else ''}\n"
226
+ f" E_net = <E1_x + E2_x{' + E3_x' if num_charges == 3 else ''}, E1_y + E2_y{' + E3_y' if num_charges == 3 else ''}>\n"
227
+ f" E_net = <{E_net[0]:.{precision}e}, {E_net[1]:.{precision}e}> N/C.\n\n"
228
+
229
+ f"**Answer:**\n"
230
+ f" The net electric field vector at point P is <{E_net[0]:.{precision}e}, {E_net[1]:.{precision}e}> N/C."
231
+ )
232
+
233
+ return question, solution
234
+
235
+
236
+ # Template 3 (Intermediate)
237
+ def template_gauss_law_symmetric():
238
+ """
239
+ Gauss's Law for Symmetric Charge Distributions (Intermediate)
240
+
241
+ Scenario:
242
+ This template tests the application of Gauss's Law to find the electric
243
+ field (E) and electric flux density (D) for a highly symmetric charge
244
+ distribution (an infinite line or an infinite sheet). The solution emphasizes
245
+ the conceptual steps: choosing an appropriate Gaussian surface, applying
246
+ Gauss's Law to find D, and then finding E.
247
+
248
+ Core Equations:
249
+ Gauss's Law: Integral(D . dS) = Q_enc
250
+ For an infinite line: D = rho_l / (2 * pi * r)
251
+ For an infinite sheet: D = rho_s / 2
252
+ Relation: D = epsilon * E
253
+
254
+ Returns:
255
+ tuple: A tuple containing:
256
+ - str: A question about the E and D fields from a charge distribution.
257
+ - str: A detailed, step-by-step solution explaining the derivation.
258
+ """
259
+ # 1. Parameterize the inputs
260
+ dist_type = random.choice(['line', 'sheet'])
261
+ epsilon_r = round(random.uniform(1.0, 6.0), 2)
262
+ epsilon = epsilon_r * EPSILON_0
263
+ precision = 3
264
+
265
+ # Initialize variables to be populated in the if/else block
266
+ question = ""
267
+ solution = ""
268
+
269
+ # --- Logic for an Infinite Line Charge ---
270
+ if dist_type == 'line':
271
+ rho_l_val = random.choice([i for i in range(-50, 51) if i != 0])
272
+ rho_l_calc = rho_l_val * 1e-9 # Convert nC/m to C/m
273
+
274
+ r_dist = round(random.uniform(0.1, 2.5), 2)
275
+ point = np.array([r_dist, 0, 0])
276
+
277
+ # Core Calculation
278
+ D_mag = rho_l_calc / (2 * math.pi * r_dist)
279
+ D_vec = D_mag * np.array([1, 0, 0]) # Direction is radial (a_r)
280
+ E_vec = D_vec / epsilon
281
+
282
+ # Generate Question and Solution Strings
283
+ question = (
284
+ f"An infinite line of charge with a uniform density rho_l = {rho_l_val} nC/m is located "
285
+ f"on the z-axis in a medium with relative permittivity epsilon_r = {epsilon_r}.\n\n"
286
+ f"Using Gauss's Law, find the electric flux density vector (D) and the electric field "
287
+ f"intensity vector (E) at the point P = {point} m."
288
+ )
289
+
290
+ solution = (
291
+ f"**Given:**\n"
292
+ f" - Infinite line charge with rho_l = {rho_l_val} nC/m = {rho_l_calc:.2e} C/m.\n"
293
+ f" - Relative permittivity epsilon_r = {epsilon_r}.\n"
294
+ f" - Observation point P = {point} m.\n\n"
295
+
296
+ f"**Step 1:** Choose a Gaussian Surface.\n"
297
+ f" For an infinite line charge, the electric field is purely radial. We choose a closed "
298
+ f"cylindrical surface of radius r = {r_dist} m and length L, coaxial with the z-axis.\n\n"
299
+
300
+ f"**Step 2:** Apply Gauss's Law.\n"
301
+ f" Gauss's Law states: Integral(D . dS) = Q_enc.\n"
302
+ f" - The flux D is perpendicular to the side surface and parallel to the top/bottom caps. "
303
+ f"Therefore, flux only passes through the curved side surface.\n"
304
+ f" - Integral(D . dS) = D_r * (Area of side) = D_r * (2 * pi * r * L).\n"
305
+ f" - The charge enclosed is Q_enc = rho_l * L.\n"
306
+ f" - Equating them: D_r * (2 * pi * r * L) = rho_l * L.\n"
307
+ f" - Solving for D_r: D_r = rho_l / (2 * pi * r).\n\n"
308
+
309
+ f"**Step 3:** Calculate the Electric Flux Density (D).\n"
310
+ f" The magnitude of D at r = {r_dist} m is:\n"
311
+ f" |D| = ({rho_l_calc:.2e}) / (2 * pi * {r_dist}) = {abs(D_mag):.{precision}e} C/m^2.\n"
312
+ f" At point P = {point} m, the direction is radial, which corresponds to the x-direction (a_x).\n"
313
+ f" Therefore, D = <{D_vec[0]:.{precision}e}, 0, 0> C/m^2.\n\n"
314
+
315
+ f"**Step 4:** Calculate the Electric Field Intensity (E).\n"
316
+ f" E = D / epsilon, where epsilon = epsilon_r * epsilon_0.\n"
317
+ f" epsilon = {epsilon_r} * {EPSILON_0:.3e} = {epsilon:.3e} F/m.\n"
318
+ f" E = <{D_vec[0]:.{precision}e}, 0, 0> / {epsilon:.3e}\n"
319
+ f" E = <{E_vec[0]:.{precision}e}, 0, 0> V/m.\n\n"
320
+ )
321
+
322
+ # --- Logic for an Infinite Sheet of Charge ---
323
+ else: # dist_type == 'sheet'
324
+ rho_s_val = random.choice([i for i in range(-50, 51) if i != 0])
325
+ rho_s_calc = rho_s_val * 1e-9 # Convert nC/m^2 to C/m^2
326
+
327
+ z_dist = round(random.uniform(0.1, 2.5), 2)
328
+ point = np.array([0, 0, z_dist])
329
+
330
+ # Core Calculation
331
+ D_mag = rho_s_calc / 2.0
332
+ D_vec = D_mag * np.array([0, 0, 1]) # Direction is normal (a_z)
333
+ E_vec = D_vec / epsilon
334
+
335
+ # Generate Question and Solution Strings
336
+ question = (
337
+ f"An infinite sheet of charge with a uniform surface density rho_s = {rho_s_val} nC/m^2 is "
338
+ f"located on the x-y plane (z=0) in a medium with relative permittivity epsilon_r = {epsilon_r}.\n\n"
339
+ f"Using Gauss's Law, find the electric flux density vector (D) and the electric field "
340
+ f"intensity vector (E) at the point P = {point} m."
341
+ )
342
+
343
+ solution = (
344
+ f"**Given:**\n"
345
+ f" - Infinite sheet charge with rho_s = {rho_s_val} nC/m^2 = {rho_s_calc:.2e} C/m^2.\n"
346
+ f" - Relative permittivity epsilon_r = {epsilon_r}.\n"
347
+ f" - Observation point P = {point} m.\n\n"
348
+
349
+ f"**Step 1:** Choose a Gaussian Surface.\n"
350
+ f" For an infinite sheet, the electric field is purely normal to the sheet. We choose a "
351
+ f"small cylindrical 'pillbox' or a rectangular box of area A, piercing the sheet and centered at z=0.\n\n"
352
+
353
+ f"**Step 2:** Apply Gauss's Law.\n"
354
+ f" Gauss's Law states: Integral(D . dS) = Q_enc.\n"
355
+ f" - The flux D is parallel to the sides of the pillbox, so flux only passes through the top and bottom surfaces.\n"
356
+ f" - Integral(D . dS) = D_z * (Top Area) + D_z * (Bottom Area) = D_z*A + D_z*A = 2*D_z*A.\n"
357
+ f" - The charge enclosed is Q_enc = rho_s * A.\n"
358
+ f" - Equating them: 2 * D_z * A = rho_s * A.\n"
359
+ f" - Solving for D_z: D_z = rho_s / 2.\n\n"
360
+
361
+ f"**Step 3:** Calculate the Electric Flux Density (D).\n"
362
+ f" The magnitude of D is independent of the distance from the sheet:\n"
363
+ f" |D| = |{rho_s_calc:.2e}| / 2 = {abs(D_mag):.{precision}e} C/m^2.\n"
364
+ f" At P = {point} m (where z > 0), the direction is normal to the sheet (a_z).\n"
365
+ f" Therefore, D = <0, 0, {D_vec[2]:.{precision}e}> C/m^2.\n\n"
366
+
367
+ f"**Step 4:** Calculate the Electric Field Intensity (E).\n"
368
+ f" E = D / epsilon, where epsilon = epsilon_r * epsilon_0.\n"
369
+ f" epsilon = {epsilon_r} * {EPSILON_0:.3e} = {epsilon:.3e} F/m.\n"
370
+ f" E = <0, 0, {D_vec[2]:.{precision}e}> / {epsilon:.3e}\n"
371
+ f" E = <0, 0, {E_vec[2]:.{precision}e}> V/m.\n\n"
372
+ )
373
+
374
+ # Final Summary for both cases
375
+ final_answer = (
376
+ f"**Answer:**\n"
377
+ f" The electric flux density is D = <{D_vec[0]:.{precision}e}, {D_vec[1]:.{precision}e}, {D_vec[2]:.{precision}e}> C/m^2.\n"
378
+ f" The electric field intensity is E = <{E_vec[0]:.{precision}e}, {E_vec[1]:.{precision}e}, {E_vec[2]:.{precision}e}> V/m."
379
+ )
380
+
381
+ solution += final_answer
382
+
383
+ return question, solution
384
+
385
+
386
+ # Template 4 (Advanced)
387
+ def template_coaxial_capacitance():
388
+ """
389
+ Capacitance of a Coaxial Cable (Advanced)
390
+
391
+ Scenario:
392
+ This template tests the ability to derive and calculate the capacitance
393
+ per unit length of a coaxial cable. The solution requires a logical,
394
+ multi-step derivation: first, finding the electric field using the
395
+ premise of Gauss's Law; second, integrating the electric field to find
396
+ the potential difference between the conductors; and finally, using the
397
+ fundamental definition of capacitance (C = Q/V).
398
+
399
+ Core Equations:
400
+ 1. E = rho_l / (2 * pi * epsilon * r)
401
+ 2. V_ab = Integral(E . dl) = (rho_l / (2 * pi * epsilon)) * ln(b/a)
402
+ 3. C' = rho_l / V_ab = (2 * pi * epsilon) / ln(b/a)
403
+
404
+ Returns:
405
+ tuple: A tuple containing:
406
+ - str: A question asking to derive and calculate the capacitance per unit length.
407
+ - str: A step-by-step solution showing the full derivation and calculation.
408
+ """
409
+ # 1. Parameterize the inputs
410
+
411
+ # Inner radius in mm, ensuring it's not too small
412
+ a_mm = round(random.uniform(0.5, 2.5), 2)
413
+
414
+ # Outer radius is 2 to 5 times larger than inner radius
415
+ b_mm = round(a_mm * random.uniform(2, 5), 2)
416
+
417
+ # Convert radii to meters for calculation
418
+ a_m = a_mm * 1e-3
419
+ b_m = b_mm * 1e-3
420
+
421
+ # Relative permittivity of a common dielectric like polyethylene or teflon
422
+ epsilon_r = round(random.uniform(2.0, 4.0), 2)
423
+
424
+ # Standardize precision for final outputs
425
+ precision = 3
426
+
427
+ # 2. Perform the core calculation
428
+
429
+ # Total permittivity of the dielectric
430
+ epsilon = epsilon_r * EPSILON_0
431
+
432
+ # Capacitance per unit length in F/m
433
+ C_prime = (2 * math.pi * epsilon) / math.log(b_m / a_m)
434
+
435
+ # Convert to a more readable unit, pF/m
436
+ C_prime_pF = C_prime * 1e12
437
+
438
+ # 3. Generate the question and solution strings
439
+
440
+ question = (
441
+ f"A coaxial cable consists of an inner conductor of radius a = {a_mm} mm and an "
442
+ f"outer conductor of radius b = {b_mm} mm.\n"
443
+ f"The space between the conductors is filled with a dielectric material with a "
444
+ f"relative permittivity of epsilon_r = {epsilon_r}.\n\n"
445
+ f"Derive the general expression for the capacitance per unit length (C') and then "
446
+ f"calculate its numerical value for this cable."
447
+ )
448
+
449
+ solution = (
450
+ f"**Given:**\n"
451
+ f" - Inner radius, a = {a_mm} mm = {a_m:.2e} m\n"
452
+ f" - Outer radius, b = {b_mm} mm = {b_m:.2e} m\n"
453
+ f" - Relative permittivity, epsilon_r = {epsilon_r}\n\n"
454
+
455
+ f"**Derivation:**\n"
456
+ f"The derivation involves three main steps: finding the electric field E, finding the "
457
+ f"potential difference V, and then finding the capacitance C' = rho_l / V.\n\n"
458
+
459
+ f"**Step 1:** Find the Electric Field (E) between the conductors.\n"
460
+ f" Assume a line charge of +rho_l (C/m) on the inner conductor and -rho_l on the outer. "
461
+ f"By applying Gauss's Law to a cylindrical surface with radius 'r' (where a < r < b), "
462
+ f"the electric field is found to be purely radial:\n"
463
+ f" E = (rho_l / (2 * pi * epsilon * r)) * a_r\n"
464
+ f" where epsilon = epsilon_r * epsilon_0.\n\n"
465
+
466
+ f"**Step 2:** Find the Potential Difference (V_ab) between the conductors.\n"
467
+ f" The potential difference is the work done to move a unit charge from the outer "
468
+ f"conductor (at r=b) to the inner conductor (at r=a) against the E field.\n"
469
+ f" V_ab = -Integral(from b to a, E . dl)\n"
470
+ f" Since the path is radial, dl = dr * a_r. The integral becomes:\n"
471
+ f" V_ab = -Integral(from b to a, [rho_l / (2 * pi * epsilon * r)] dr)\n"
472
+ f" V_ab = -[rho_l / (2 * pi * epsilon)] * [ln(r)](from b to a)\n"
473
+ f" V_ab = -[rho_l / (2 * pi * epsilon)] * (ln(a) - ln(b))\n"
474
+ f" Using log properties, ln(a) - ln(b) = -ln(b/a), this simplifies to:\n"
475
+ f" V_ab = (rho_l / (2 * pi * epsilon)) * ln(b/a)\n\n"
476
+
477
+ f"**Step 3:** Derive the Capacitance per Unit Length (C').\n"
478
+ f" Capacitance per unit length is defined as the charge per unit length (rho_l) "
479
+ f"divided by the potential difference (V_ab).\n"
480
+ f" C' = rho_l / V_ab\n"
481
+ f" C' = rho_l / [(rho_l / (2 * pi * epsilon)) * ln(b/a)]\n"
482
+ f" The rho_l terms cancel, leaving the final expression:\n"
483
+ f" C' = (2 * pi * epsilon) / ln(b/a)\n\n"
484
+
485
+ f"**Step 4:** Calculate the final value.\n"
486
+ f" First, find the permittivity of the dielectric:\n"
487
+ f" epsilon = {epsilon_r} * {EPSILON_0:.4e} = {epsilon:.4e} F/m.\n"
488
+ f" Now, plug the values into the derived formula:\n"
489
+ f" C' = (2 * pi * {epsilon:.4e}) / ln({b_mm} / {a_mm})\n"
490
+ f" C' = (2 * pi * {epsilon:.4e}) / ln({b_m/a_m:.{precision}f})\n"
491
+ f" C' = (2 * pi * {epsilon:.4e}) / {math.log(b_m/a_m):.{precision}f}\n"
492
+ f" C' = {C_prime:.{precision}e} F/m.\n\n"
493
+
494
+ f"**Answer:**\n"
495
+ f" The derived expression for capacitance per unit length is C' = (2 * pi * epsilon) / ln(b/a).\n"
496
+ f" The numerical value is {C_prime:.{precision}e} F/m, which is equivalent to "
497
+ f"**{C_prime_pF:.{precision-1}f} pF/m**."
498
+ )
499
+
500
+ return question, solution
501
+
502
+
503
+ def main():
504
+ """
505
+ Generate numerous instances of each electrostatics template
506
+ with different random seeds and write the results to a JSONL file.
507
+ """
508
+ import json
509
+ import os
510
+
511
+ # Define the output path (Modify this path according to where you are running the code from)
512
+ output_file = "testset/electrical_engineering/electromagnetics_and_waves/electrostatics.jsonl"
513
+
514
+ # Create the directory if it doesn't exist
515
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
516
+
517
+ # List of template functions with their ID and level
518
+ templates = [
519
+ (template_coulombs_law, "coulombs_law_two_charges", "Easy"),
520
+ (template_superposition_electric_field, "superposition_electric_field", "Intermediate"),
521
+ (template_gauss_law_symmetric, "gauss_law_symmetric_charge", "Intermediate"),
522
+ (template_coaxial_capacitance, "coaxial_cable_capacitance", "Advanced"),
523
+ ]
524
+
525
+ # List to store all generated problems
526
+ all_problems = []
527
+
528
+ # Generate problems for each template
529
+ for template_func, id_name, level in templates:
530
+ for _ in range(50):
531
+ # Generate a unique seed for each problem
532
+ seed = random.randint(1_000_000_000, 4_000_000_000)
533
+ random.seed(seed)
534
+
535
+ # Generate the problem and solution
536
+ question, solution = template_func()
537
+
538
+ # Create a JSON entry
539
+ problem_entry = {
540
+ "seed": seed,
541
+ "branch": "electrical_engineering",
542
+ "domain": "electromagnetics_and_waves",
543
+ "area": "electrostatics",
544
+ "id": id_name,
545
+ "level": level,
546
+ "question": question,
547
+ "solution": solution
548
+ }
549
+
550
+ # Add to the list of problems
551
+ all_problems.append(problem_entry)
552
+
553
+ # Shuffle the problems to mix templates and levels
554
+ random.shuffle(all_problems)
555
+
556
+ # Write all problems to a .jsonl file
557
+ with open(output_file, "w") as file:
558
+ for problem in all_problems:
559
+ file.write(json.dumps(problem))
560
+ file.write("\n")
561
+
562
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
563
+
564
+
565
+ if __name__ == "__main__":
566
+ main()
data/templates/branches/electrical_engineering/electromagnetics_and_waves/magnetostatics.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+
4
+
5
+ # Template 1 (Easy)
6
+ def template_lorentz_force():
7
+ """
8
+ Lorentz Force on a Moving Charge
9
+
10
+ Scenario:
11
+ This template tests the fundamental calculation of the magnetic force on a moving
12
+ point charge in a uniform magnetic field. It requires unit conversion and the
13
+ correct application of the vector cross product, which is a core skill in
14
+ electromagnetics.
15
+
16
+ Core Equation:
17
+ F_m = q * (u x B)
18
+
19
+ Returns:
20
+ tuple: A tuple containing:
21
+ - str: A question asking for the force vector and magnitude.
22
+ - str: A step-by-step solution.
23
+ """
24
+ # 1. Parameterize the inputs with random values
25
+
26
+ # Generate charge in microcoulombs (uC)
27
+ q_uC = round(random.uniform(1.0, 100.0), 2)
28
+ if random.choice([True, False]):
29
+ q_uC *= -1
30
+
31
+ # Generate velocity vector components in m/s
32
+ u_vec = [random.randint(-50, 50) for _ in range(3)]
33
+
34
+ # Generate magnetic field vector components in millitesla (mT)
35
+ B_vec_mT = [round(random.uniform(-200.0, 200.0), 2) for _ in range(3)]
36
+
37
+ # Standardize precision for final outputs
38
+ precision = 3
39
+
40
+ # 2. Perform the core calculation
41
+
42
+ # Convert units for calculation
43
+ q_C = q_uC * 1e-6
44
+ B_vec_T = [b * 1e-3 for b in B_vec_mT]
45
+
46
+ # Calculate the cross product: u x B
47
+ cross_product_x = u_vec[1] * B_vec_T[2] - u_vec[2] * B_vec_T[1]
48
+ cross_product_y = u_vec[2] * B_vec_T[0] - u_vec[0] * B_vec_T[2]
49
+ cross_product_z = u_vec[0] * B_vec_T[1] - u_vec[1] * B_vec_T[0]
50
+
51
+ # Calculate the force vector: F = q * (u x B)
52
+ force_x = q_C * cross_product_x
53
+ force_y = q_C * cross_product_y
54
+ force_z = q_C * cross_product_z
55
+
56
+ # Calculate the magnitude of the force
57
+ force_magnitude = math.sqrt(force_x**2 + force_y**2 + force_z**2)
58
+
59
+ # 3. Generate the question and solution strings
60
+
61
+ # Helper function to format vectors for display
62
+ def format_vector(v, units=""):
63
+ return f"({v[0]} x_hat + {v[1]} y_hat + {v[2]} z_hat) {units}".strip()
64
+
65
+ question = (
66
+ f"A point charge of {q_uC} uC has a velocity of u = {format_vector(u_vec, 'm/s')} "
67
+ f"in a uniform magnetic field described by B = {format_vector(B_vec_mT, 'mT')}.\n\n"
68
+ f"Determine the magnetic force vector F_m acting on the charge and its magnitude."
69
+ )
70
+
71
+ solution = (
72
+ f"**Given:**\n"
73
+ f" - Charge (q): {q_uC} uC\n"
74
+ f" - Velocity (u): {format_vector(u_vec, 'm/s')}\n"
75
+ f" - Magnetic Field (B): {format_vector(B_vec_mT, 'mT')}\n\n"
76
+
77
+ f"**Step 1:** Convert Units to SI\n"
78
+ f" First, we convert the given values to standard SI units for the calculation.\n"
79
+ f" - Charge in Coulombs: q = {q_uC} * 1e-6 = {q_C:.2e} C\n"
80
+ f" - Magnetic Field in Tesla: B = ({B_vec_T[0]:.2e} x_hat + {B_vec_T[1]:.2e} y_hat + {B_vec_T[2]:.2e} z_hat) T\n\n"
81
+
82
+ f"**Step 2:** Calculate the Cross Product (u x B)\n"
83
+ f" The force is determined by the formula F_m = q * (u x B). We start by calculating the cross product.\n"
84
+ f" u x B = [ (u_y * B_z - u_z * B_y) x_hat + (u_z * B_x - u_x * B_z) y_hat + (u_x * B_y - u_y * B_x) z_hat ]\n"
85
+ f" (u x B)_x = ({u_vec[1]}) * ({B_vec_T[2]:.2e}) - ({u_vec[2]}) * ({B_vec_T[1]:.2e}) = {cross_product_x:.4f}\n"
86
+ f" (u x B)_y = ({u_vec[2]}) * ({B_vec_T[0]:.2e}) - ({u_vec[0]}) * ({B_vec_T[2]:.2e}) = {cross_product_y:.4f}\n"
87
+ f" (u x B)_z = ({u_vec[0]}) * ({B_vec_T[1]:.2e}) - ({u_vec[1]}) * ({B_vec_T[0]:.2e}) = {cross_product_z:.4f}\n"
88
+ f" So, u x B = ({round(cross_product_x, precision)} x_hat + {round(cross_product_y, precision)} y_hat + {round(cross_product_z, precision)} z_hat) T*m/s\n\n"
89
+
90
+ f"**Step 3:** Calculate the Force Vector (F_m)\n"
91
+ f" Now, multiply the cross product by the charge q.\n"
92
+ f" F_m = ({q_C:.2e} C) * ({round(cross_product_x, precision)} x_hat + {round(cross_product_y, precision)} y_hat + {round(cross_product_z, precision)} z_hat)\n"
93
+ f" F_m = ({force_x:.{precision}e} x_hat + {force_y:.{precision}e} y_hat + {force_z:.{precision}e} z_hat) N\n\n"
94
+
95
+ f"**Step 4:** Calculate the Magnitude of the Force\n"
96
+ f" The magnitude is the square root of the sum of the squares of the components.\n"
97
+ f" |F_m| = sqrt( ({force_x:.2e})^2 + ({force_y:.2e})^2 + ({force_z:.2e})^2 )\n"
98
+ f" |F_m| = {force_magnitude:.{precision}e} N\n\n"
99
+
100
+ f"**Answer:**\n"
101
+ f" The magnetic force vector is F_m = ({force_x:.{precision}e} x_hat + {force_y:.{precision}e} y_hat + {force_z:.{precision}e} z_hat) N.\n"
102
+ f" The magnitude of the force is |F_m| = {force_magnitude:.{precision}e} N."
103
+ )
104
+
105
+ return question, solution
106
+
107
+
108
+ def main():
109
+ """
110
+ Generate numerous instances of each magnetostatics template
111
+ with different random seeds and write the results to a JSONL file.
112
+ """
113
+ import json
114
+ import os
115
+
116
+ # Define the output path (Modify this path according to where you are running the code from)
117
+ output_file = "testset/electrical_engineering/electromagnetics_and_waves/magnetostatics.jsonl"
118
+
119
+ # Create the directory if it doesn't exist
120
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
121
+
122
+ # List of template functions with their ID and level
123
+ templates = [
124
+ (template_lorentz_force, "lorentz_force", "Easy"),
125
+ ]
126
+
127
+ # List to store all generated problems
128
+ all_problems = []
129
+
130
+ # Generate problems for each template
131
+ for template_func, id_name, level in templates:
132
+ for _ in range(50):
133
+ # Generate a unique seed for each problem
134
+ seed = random.randint(1_000_000_000, 4_000_000_000)
135
+ random.seed(seed)
136
+
137
+ # Generate the problem and solution
138
+ question, solution = template_func()
139
+
140
+ # Create a JSON entry
141
+ problem_entry = {
142
+ "seed": seed,
143
+ "branch": "electrical_engineering",
144
+ "domain": "electromagnetics_and_waves",
145
+ "area": "magnetostatics",
146
+ "id": id_name,
147
+ "level": level,
148
+ "question": question,
149
+ "solution": solution
150
+ }
151
+
152
+ # Add to the list of problems
153
+ all_problems.append(problem_entry)
154
+
155
+ # Shuffle the problems to mix templates and levels
156
+ random.shuffle(all_problems)
157
+
158
+ # Write all problems to a .jsonl file
159
+ with open(output_file, "w") as file:
160
+ for problem in all_problems:
161
+ file.write(json.dumps(problem))
162
+ file.write("\n")
163
+
164
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
165
+
166
+
167
+ if __name__ == "__main__":
168
+ main()
data/templates/branches/electrical_engineering/electromagnetics_and_waves/waves_and_phasors.py ADDED
@@ -0,0 +1,650 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ from data.templates.branches.electrical_engineering.constants import C0, MEDIA_VELOCITIES
4
+
5
+
6
+ # Template 1 (Easy)
7
+ def template_wave_parameters_basic():
8
+ """
9
+ Wave Parameter Fundamentals Calculation
10
+
11
+ Scenario:
12
+ This template explores the fundamental relationships that define a sinusoidal
13
+ traveling wave. These parameters (frequency, wavelength, period, etc.) describe
14
+ a wave's oscillatory behavior in both time and space. The problem provides the
15
+ wave's propagation speed (defined by its medium) and one other key parameter
16
+ (either frequency or wavelength), requiring the calculation of all other
17
+ related properties.
18
+
19
+ Core Equations:
20
+ u_p = f * lambda
21
+ omega = 2 * pi * f
22
+ T = 1 / f
23
+ k = 2 * pi / lambda
24
+
25
+ Returns:
26
+ tuple: A tuple containing:
27
+ - str: A question asking to compute various wave parameters.
28
+ - str: A step-by-step solution showing the calculations.
29
+ """
30
+ # 1. Parameterize the inputs with random values
31
+ medium_name, u_p = random.choice(list(MEDIA_VELOCITIES.items()))
32
+ start_with_frequency = random.choice([True, False])
33
+
34
+ if start_with_frequency:
35
+ # Scenario 1: Given frequency and medium
36
+ f_mhz = round(random.uniform(50, 500), 1)
37
+ f = f_mhz * 1e6
38
+
39
+ # 2. Perform the core calculations
40
+ omega = 2 * math.pi * f
41
+ T = 1 / f
42
+ lambda_ = u_p / f
43
+ k = 2 * math.pi / lambda_
44
+
45
+ # 3. Generate the question and solution strings (Plain Text)
46
+ question = (
47
+ f"A sinusoidal wave with a frequency of {f_mhz} MHz is propagating "
48
+ f"through {medium_name}. Given that the phase velocity in this medium is "
49
+ f"{u_p:.2e} m/s, calculate the wave's:\n"
50
+ f"a) Angular frequency (omega)\n"
51
+ f"b) Period (T)\n"
52
+ f"c) Wavelength (lambda)\n"
53
+ f"d) Wavenumber (k)"
54
+ )
55
+
56
+ solution = (
57
+ f"**Given:**\n"
58
+ f"- Frequency (f) = {f_mhz} MHz = {f:.2e} Hz\n"
59
+ f"- Medium = {medium_name}\n"
60
+ f"- Phase Velocity (u_p) = {u_p:.2e} m/s\n\n"
61
+
62
+ f"**Step 1:** Calculate the Angular Frequency (omega)\n"
63
+ f"The angular frequency is related to frequency by omega = 2 * pi * f.\n"
64
+ f" omega = 2 * {math.pi:.5f} * ({f:.2e} Hz) = {omega:.2e} rad/s\n\n"
65
+
66
+ f"**Step 2:** Calculate the Period (T)\n"
67
+ f"The period is the inverse of the frequency, T = 1 / f.\n"
68
+ f" T = 1 / ({f:.2e} Hz) = {T:.2e} s\n\n"
69
+
70
+ f"**Step 3:** Calculate the Wavelength (lambda)\n"
71
+ f"The wavelength is found using the phase velocity, lambda = u_p / f.\n"
72
+ f" lambda = ({u_p:.2e} m/s) / ({f:.2e} Hz) = {round(lambda_, 3)} m\n\n"
73
+
74
+ f"**Step 4:** Calculate the Wavenumber (k)\n"
75
+ f"The wavenumber (or phase constant) is given by k = 2 * pi / lambda.\n"
76
+ f" k = 2 * {math.pi:.5f} / {round(lambda_, 3)} m = {round(k, 2)} rad/m\n\n"
77
+
78
+ f"**Answer:**\n"
79
+ f"- Angular Frequency (omega): {omega:.2e} rad/s\n"
80
+ f"- Period (T): {T:.2e} s\n"
81
+ f"- Wavelength (lambda): {round(lambda_, 3)} m\n"
82
+ f"- Wavenumber (k): {round(k, 2)} rad/m"
83
+ )
84
+
85
+ else:
86
+ # Scenario 2: Given wavelength and medium
87
+ lambda_ = round(random.uniform(0.1, 2.0), 2)
88
+
89
+ # 2. Perform the core calculations
90
+ f = u_p / lambda_
91
+ omega = 2 * math.pi * f
92
+ T = 1 / f
93
+ k = 2 * math.pi / lambda_
94
+
95
+ # 3. Generate the question and solution strings (Plain Text)
96
+ question = (
97
+ f"An electromagnetic wave traveling in {medium_name} is observed to have "
98
+ f"a wavelength of {lambda_} m. Given that the phase velocity in this "
99
+ f"medium is {u_p:.2e} m/s, determine the wave's:\n"
100
+ f"a) Frequency (f)\n"
101
+ f"b) Angular frequency (omega)\n"
102
+ f"c) Period (T)\n"
103
+ f"d) Wavenumber (k)"
104
+ )
105
+
106
+ solution = (
107
+ f"**Given:**\n"
108
+ f"- Wavelength (lambda) = {lambda_} m\n"
109
+ f"- Medium = {medium_name}\n"
110
+ f"- Phase Velocity (u_p) = {u_p:.2e} m/s\n\n"
111
+
112
+ f"**Step 1:** Calculate the Frequency (f)\n"
113
+ f"Frequency is found using the relation u_p = f * lambda, so f = u_p / lambda.\n"
114
+ f" f = ({u_p:.2e} m/s) / ({lambda_} m) = {f:.2e} Hz = {f/1e6:.2f} MHz\n\n"
115
+
116
+ f"**Step 2:** Calculate the Angular Frequency (omega)\n"
117
+ f"The angular frequency is omega = 2 * pi * f.\n"
118
+ f" omega = 2 * {math.pi:.5f} * ({f:.2e} Hz) = {omega:.2e} rad/s\n\n"
119
+
120
+ f"**Step 3:** Calculate the Period (T)\n"
121
+ f"The period is the inverse of the frequency, T = 1 / f.\n"
122
+ f" T = 1 / ({f:.2e} Hz) = {T:.2e} s\n\n"
123
+
124
+ f"**Step 4:** Calculate the Wavenumber (k)\n"
125
+ f"The wavenumber is given by k = 2 * pi / lambda.\n"
126
+ f" k = 2 * {math.pi:.5f} / {lambda_} m = {round(k, 2)} rad/m\n\n"
127
+
128
+ f"**Answer:**\n"
129
+ f"- Frequency (f): {f/1e6:.2f} MHz\n"
130
+ f"- Angular Frequency (omega): {omega:.2e} rad/s\n"
131
+ f"- Period (T): {T:.2e} s\n"
132
+ f"- Wavenumber (k): {round(k, 2)} rad/m"
133
+ )
134
+
135
+ return question, solution
136
+
137
+
138
+ # Template 2 (Easy)
139
+ def template_time_to_phasor():
140
+ """
141
+ Time-Domain to Phasor Conversion
142
+
143
+ Scenario:
144
+ This template tests the ability to convert a sinusoidal time-domain function
145
+ into its corresponding phasor representation. This is a foundational skill for
146
+ AC circuit analysis and wave analysis. The conversion requires identifying the
147
+ amplitude and phase, and applying a phase shift if the function is a sine wave.
148
+
149
+ Core Equations:
150
+ For v(t) = A * cos(omega*t + phi):
151
+ Phasor V = A * exp(j*phi) = A < phi
152
+
153
+ Identity: sin(theta) = cos(theta - 90 deg)
154
+
155
+ Returns:
156
+ tuple: A tuple containing:
157
+ - str: A question asking for the phasor form of a given time-domain signal.
158
+ - str: A step-by-step solution showing the conversion.
159
+ """
160
+ # 1. Parameterize the inputs with random values
161
+ amplitude = round(random.uniform(5.0, 150.0), 2)
162
+ omega = random.randint(100, 1000)
163
+ func_type = random.choice(['cos', 'sin'])
164
+ use_degrees = random.choice([True, False])
165
+
166
+ # Standardize precision for all calculations and outputs
167
+ precision = 2
168
+
169
+ # Generate phase in either degrees or radians
170
+ if use_degrees:
171
+ phi_deg = round(random.uniform(-180.0, 180.0), 1)
172
+ phi_rad = math.radians(phi_deg)
173
+ phi_str = f"{phi_deg} deg"
174
+ else:
175
+ phi_rad = round(random.uniform(-math.pi, math.pi), precision)
176
+ phi_deg = math.degrees(phi_rad)
177
+ phi_str = f"{phi_rad} rad"
178
+
179
+ # Construct the time-domain function string for the question
180
+ time_domain_expr = f"{amplitude} * {func_type}({omega}*t + {phi_str})"
181
+
182
+ # 2. Perform the core calculation
183
+
184
+ # The final amplitude of the phasor is the amplitude of the signal
185
+ phasor_amplitude = amplitude
186
+
187
+ # The final phase depends on whether the function is cos or sin
188
+ if func_type == 'cos':
189
+ phasor_phi_deg = phi_deg
190
+ phasor_phi_rad = phi_rad
191
+ conversion_step = (
192
+ "**Step 2:** Identify the function type.\n"
193
+ " The function is a cosine, which is the standard reference for phasors. "
194
+ "No phase adjustment is needed.\n"
195
+ f" The initial phase is {round(phi_deg, precision)} degrees.\n"
196
+ )
197
+ else: # func_type == 'sin'
198
+ phasor_phi_deg = phi_deg - 90
199
+ phasor_phi_rad = math.radians(phasor_phi_deg)
200
+ conversion_step = (
201
+ "**Step 2:** Convert the sine function to cosine.\n"
202
+ " The sine function leads the cosine function by 90 degrees. To convert, "
203
+ "we use the identity sin(theta) = cos(theta - 90 deg).\n"
204
+ f" New phase = (Initial Phase) - 90 deg = {round(phi_deg, precision)} - 90 = {round(phasor_phi_deg, precision)} degrees.\n"
205
+ )
206
+
207
+ # Normalize the final phase angle to be between -180 and 180 degrees
208
+ phasor_phi_deg = (phasor_phi_deg + 180) % 360 - 180
209
+ phasor_phi_rad = math.radians(phasor_phi_deg)
210
+
211
+ # Calculate rectangular components
212
+ real_part = phasor_amplitude * math.cos(phasor_phi_rad)
213
+ imag_part = phasor_amplitude * math.sin(phasor_phi_rad)
214
+
215
+ # 3. Generate the question and solution strings
216
+ question = (
217
+ f"A signal is described by the time-domain function:\n"
218
+ f" v(t) = {time_domain_expr}\n\n"
219
+ f"Determine the phasor representation of this signal in both polar (A < phi) "
220
+ f"and rectangular (x + jy) forms. Use degrees for the phase angle."
221
+ )
222
+
223
+ solution = (
224
+ f"**Given:**\n"
225
+ f" The time-domain signal is v(t) = {time_domain_expr}.\n\n"
226
+
227
+ f"**Step 1:** Extract the amplitude and initial phase.\n"
228
+ f" By inspection, the amplitude (A) is {amplitude}.\n"
229
+ f" The initial phase is {phi_str}.\n\n"
230
+
231
+ f"{conversion_step}\n"
232
+
233
+ f"**Step 3:** Write the phasor in polar form.\n"
234
+ f" Phase angles are conventionally expressed in the range [-180°, 180°].\n"
235
+ f" The phasor has the signal's amplitude and the adjusted phase.\n"
236
+ f" Phasor V = {round(phasor_amplitude, precision)} < {round(phasor_phi_deg, precision)} degrees.\n\n"
237
+
238
+ f"**Step 4:** Convert the polar form to rectangular form (x + jy).\n"
239
+ f" x (real part) = A * cos(phi) = {round(phasor_amplitude, precision)} * cos({round(phasor_phi_deg, precision)} deg) = {round(real_part, precision)}\n"
240
+ f" y (imaginary part) = A * sin(phi) = {round(phasor_amplitude, precision)} * sin({round(phasor_phi_deg, precision)} deg) = {round(imag_part, precision)}\n"
241
+ f" Phasor V = {round(real_part, precision)} + j{round(imag_part, precision)}\n\n"
242
+
243
+ f"**Answer:**\n"
244
+ f" The phasor representation is {round(phasor_amplitude, precision)} < {round(phasor_phi_deg, precision)} degrees, "
245
+ f"which is equivalent to {round(real_part, precision)} + j{round(imag_part, precision)}."
246
+ )
247
+
248
+ return question, solution
249
+
250
+
251
+ # Template 3 (Intermediate)
252
+ def template_wave_equation_interpretation():
253
+ """
254
+ Wave Equation Interpretation
255
+
256
+ Scenario:
257
+ This template tests the ability to extract fundamental wave parameters directly
258
+ from the mathematical expression of a traveling wave. It requires matching the
259
+ given equation to the standard form to identify key coefficients (amplitude,
260
+ angular frequency, wavenumber) and then using them to calculate related properties.
261
+
262
+ Core Equations:
263
+ f = omega / (2 * pi)
264
+ lambda = 2 * pi / k
265
+ u_p = omega / k
266
+
267
+ Returns:
268
+ tuple: A tuple containing:
269
+ - str: A question presenting a wave equation and asking for its properties.
270
+ - str: A step-by-step solution showing the analysis and calculations.
271
+ """
272
+ # 1. Parameterize the inputs with random values
273
+ amplitude = random.randint(10, 200)
274
+
275
+ # Generate a realistic angular frequency (omega)
276
+ omega_multiple = random.randint(2, 9)
277
+ omega = omega_multiple * math.pi * 1e8
278
+
279
+ # Generate a realistic phase velocity (u_p) by choosing a refractive index
280
+ # This ensures omega and k are physically consistent.
281
+ refractive_index = round(random.uniform(1.0, 2.5), 2)
282
+ u_p = C0 / refractive_index
283
+
284
+ # Calculate wavenumber (k) based on omega and u_p
285
+ k = omega / u_p
286
+
287
+ # Randomly determine the direction of propagation and phase
288
+ direction_sign_str = random.choice(['+', '-'])
289
+ phi_deg = random.randint(-180, 180)
290
+
291
+ # Determine direction text for the solution
292
+ if direction_sign_str == '-':
293
+ direction_text = "the positive z-direction"
294
+ else:
295
+ direction_text = "the negative z-direction"
296
+
297
+ # Construct the wave equation string for the question
298
+ # Format omega for better readability in the question
299
+ omega_str = f"{omega_multiple}pi x 10^8"
300
+ wave_equation = (
301
+ f"E(z, t) = {amplitude} * cos({omega_str}*t {direction_sign_str} {round(k, 2)}*z + {phi_deg} deg) V/m"
302
+ )
303
+
304
+ # 2. Perform the core calculations for the solution
305
+ frequency = omega / (2 * math.pi)
306
+ wavelength = (2 * math.pi) / k
307
+
308
+ # 3. Generate the question and solution strings
309
+ question = (
310
+ f"An electric field wave is described by the following equation:\n"
311
+ f" {wave_equation}\n\n"
312
+ f"Based on this expression, determine the following properties of the wave:\n"
313
+ f"a) Amplitude (A)\n"
314
+ f"b) Direction of propagation\n"
315
+ f"c) Frequency (f)\n"
316
+ f"d) Wavelength (lambda)\n"
317
+ f"e) Phase velocity (u_p)"
318
+ )
319
+
320
+ solution = (
321
+ f"**Given:**\n"
322
+ f" The wave equation is E(z, t) = {wave_equation}.\n\n"
323
+
324
+ f"**Step 1:** Compare the equation to the standard form.\n"
325
+ f" The standard form for a traveling wave is A * cos(omega*t +/- k*z + phi).\n"
326
+ f" By matching the terms, we can extract the coefficients:\n"
327
+ f" - Amplitude (A) = {amplitude} V/m\n"
328
+ f" - Angular Frequency (omega) = {omega_str} rad/s = {omega:.3e} rad/s\n"
329
+ f" - Wavenumber (k) = {round(k, 2)} rad/m\n"
330
+ f" - The sign between the t and z terms is '{direction_sign_str}'.\n\n"
331
+
332
+ f"**Step 2:** Determine the Amplitude and Direction of Propagation.\n"
333
+ f" The amplitude (A) is the value multiplying the cosine function, which is {amplitude} V/m.\n"
334
+ f" The direction is determined by the sign in front of the 'k*z' term. A minus sign (-) indicates propagation in the positive direction, while a plus sign (+) indicates the negative direction.\n"
335
+ f" Therefore, the wave is traveling in {direction_text}.\n\n"
336
+
337
+ f"**Step 3:** Calculate the Frequency (f).\n"
338
+ f" Frequency is related to angular frequency by f = omega / (2 * pi).\n"
339
+ f" f = ({omega:.3e} rad/s) / (2 * pi) = {frequency:.3e} Hz = {frequency/1e6:.2f} MHz.\n\n"
340
+
341
+ f"**Step 4:** Calculate the Wavelength (lambda).\n"
342
+ f" Wavelength is related to the wavenumber by lambda = 2 * pi / k.\n"
343
+ f" lambda = (2 * pi) / ({round(k, 2)} rad/m) = {round(wavelength, 3)} m.\n\n"
344
+
345
+ f"**Step 5:** Calculate the Phase Velocity (u_p).\n"
346
+ f" Phase velocity is the speed of the wave, given by u_p = omega / k.\n"
347
+ f" u_p = ({omega:.3e} rad/s) / ({round(k, 2)} rad/m) = {u_p:.3e} m/s.\n\n"
348
+
349
+ f"**Answer:**\n"
350
+ f" - Amplitude: {amplitude} V/m\n"
351
+ f" - Direction of Propagation: {direction_text}\n"
352
+ f" - Frequency: {frequency/1e6:.2f} MHz\n"
353
+ f" - Wavelength: {round(wavelength, 3)} m\n"
354
+ f" - Phase Velocity: {u_p:.3e} m/s"
355
+ )
356
+
357
+ return question, solution
358
+
359
+
360
+ # Template 4 (Intermediate)
361
+ def template_phasor_addition():
362
+ """
363
+ Phasor Addition of Sinusoidal Waves
364
+
365
+ Scenario:
366
+ This template assesses the ability to add two sinusoidal waves of the same
367
+ frequency. The standard method is to convert both signals into their phasor
368
+ representations, perform complex number addition in rectangular form, convert
369
+ the result back to polar form, and finally express it as a time-domain signal.
370
+
371
+ Core Equations:
372
+ V_phasor = A < phi
373
+ x = A * cos(phi)
374
+ y = A * sin(phi)
375
+ A_total = sqrt(x_total^2 + y_total^2)
376
+ phi_total = atan2(y_total, x_total)
377
+ v_total(t) = A_total * cos(omega*t + phi_total)
378
+
379
+ Returns:
380
+ tuple: A tuple containing:
381
+ - str: A question asking for the sum of two time-domain signals.
382
+ - str: A step-by-step solution using the phasor addition method.
383
+ """
384
+ # 1. Parameterize the inputs
385
+ precision = 2
386
+
387
+ # Amplitudes
388
+ A1 = round(random.uniform(10.0, 50.0), precision)
389
+ A2 = round(random.uniform(10.0, 50.0), precision)
390
+
391
+ # Phases in degrees
392
+ phi1_deg = round(random.uniform(-180.0, 180.0), 1)
393
+ phi2_deg = round(random.uniform(-180.0, 180.0), 1)
394
+
395
+ # Shared angular frequency
396
+ omega = random.randint(100, 500)
397
+
398
+ # Function types (cos or sin)
399
+ func_type1 = random.choice(['cos', 'sin'])
400
+ func_type2 = random.choice(['cos', 'sin'])
401
+
402
+ # 2. Generate the question string
403
+ v1_str = f"{A1} * {func_type1}({omega}*t + {phi1_deg} deg)"
404
+ v2_str = f"{A2} * {func_type2}({omega}*t + {phi2_deg} deg)"
405
+
406
+ question = (
407
+ f"Two signals, v1(t) and v2(t), are defined as:\n"
408
+ f" v1(t) = {v1_str}\n"
409
+ f" v2(t) = {v2_str}\n\n"
410
+ f"Find the sum, v_total(t) = v1(t) + v2(t), using the phasor method. "
411
+ f"Express the final answer as a single cosine function."
412
+ )
413
+
414
+ # 3. Perform the calculations for the solution
415
+
416
+ # --- Phasor 1 Conversion ---
417
+ phi1_adj_deg = phi1_deg - 90 if func_type1 == 'sin' else phi1_deg
418
+ phi1_rad = math.radians(phi1_adj_deg)
419
+ x1 = A1 * math.cos(phi1_rad)
420
+ y1 = A1 * math.sin(phi1_rad)
421
+
422
+ # --- Phasor 2 Conversion ---
423
+ phi2_adj_deg = phi2_deg - 90 if func_type2 == 'sin' else phi2_deg
424
+ phi2_rad = math.radians(phi2_adj_deg)
425
+ x2 = A2 * math.cos(phi2_rad)
426
+ y2 = A2 * math.sin(phi2_rad)
427
+
428
+ # --- Addition ---
429
+ x_total = x1 + x2
430
+ y_total = y1 + y2
431
+
432
+ # --- Convert Sum back to Polar ---
433
+ A_total = math.hypot(x_total, y_total) # sqrt(x^2 + y^2)
434
+ phi_total_rad = math.atan2(y_total, x_total)
435
+ phi_total_deg = math.degrees(phi_total_rad)
436
+
437
+ # --- Final time-domain expression ---
438
+ v_total_str = f"{round(A_total, precision)} * cos({omega}*t + {round(phi_total_deg, precision)} deg)"
439
+
440
+ # 4. Generate the solution string
441
+
442
+ # Helper strings for conversion steps
443
+ conv1_step = f" Since v1(t) is a {func_type1} function, we adjust the phase: {phi1_deg} - 90 = {round(phi1_adj_deg, 1)} deg." if func_type1 == 'sin' else f" Since v1(t) is a cosine function, no phase adjustment is needed. Phase = {phi1_deg} deg."
444
+ conv2_step = f" Since v2(t) is a {func_type2} function, we adjust the phase: {phi2_deg} - 90 = {round(phi2_adj_deg, 1)} deg." if func_type2 == 'sin' else f" Since v2(t) is a cosine function, no phase adjustment is needed. Phase = {phi2_deg} deg."
445
+
446
+ solution = (
447
+ f"**Given:**\n"
448
+ f" v1(t) = {v1_str}\n"
449
+ f" v2(t) = {v2_str}\n\n"
450
+
451
+ f"**Step 1:** Convert v1(t) to its phasor form V1.\n"
452
+ f"{conv1_step}\n"
453
+ f" The polar form is V1 = {A1} < {round(phi1_adj_deg, 1)} deg.\n"
454
+ f" Convert to rectangular form (x1 + jy1):\n"
455
+ f" x1 = {A1} * cos({round(phi1_adj_deg, 1)}) = {round(x1, precision)}\n"
456
+ f" y1 = {A1} * sin({round(phi1_adj_deg, 1)}) = {round(y1, precision)}\n"
457
+ f" So, V1 = {round(x1, precision)} + j{round(y1, precision)}.\n\n"
458
+
459
+ f"**Step 2:** Convert v2(t) to its phasor form V2.\n"
460
+ f"{conv2_step}\n"
461
+ f" The polar form is V2 = {A2} < {round(phi2_adj_deg, 1)} deg.\n"
462
+ f" Convert to rectangular form (x2 + jy2):\n"
463
+ f" x2 = {A2} * cos({round(phi2_adj_deg, 1)}) = {round(x2, precision)}\n"
464
+ f" y2 = {A2} * sin({round(phi2_adj_deg, 1)}) = {round(y2, precision)}\n"
465
+ f" So, V2 = {round(x2, precision)} + j{round(y2, precision)}.\n\n"
466
+
467
+ f"**Step 3:** Add the phasors in rectangular form: V_total = V1 + V2.\n"
468
+ f" V_total = ({round(x1, precision)} + j{round(y1, precision)}) + ({round(x2, precision)} + j{round(y2, precision)})\n"
469
+ f" V_total = ({round(x1, precision)} + {round(x2, precision)}) + j({round(y1, precision)} + {round(y2, precision)})\n"
470
+ f" V_total = {round(x_total, precision)} + j{round(y_total, precision)}.\n\n"
471
+
472
+ f"**Step 4:** Convert the resultant phasor V_total back to polar form (A < phi).\n"
473
+ f" A_total = sqrt({round(x_total, precision)}^2 + {round(y_total, precision)}^2) = {round(A_total, precision)}\n"
474
+ f" phi_total = atan2({round(y_total, precision)}, {round(x_total, precision)}) = {round(phi_total_deg, precision)} deg\n"
475
+ f" So, V_total = {round(A_total, precision)} < {round(phi_total_deg, precision)} deg.\n\n"
476
+
477
+ f"**Step 5:** Convert the final phasor back to the time domain.\n"
478
+ f" The resulting signal has amplitude A_total, phase phi_total, and the original angular frequency omega.\n"
479
+ f" v_total(t) = {v_total_str}\n\n"
480
+
481
+ f"**Answer:**\n"
482
+ f" The sum of the two signals is v_total(t) = {v_total_str}."
483
+ )
484
+
485
+ return question, solution
486
+
487
+
488
+ # Template 5 (Advanced)
489
+ def template_standing_wave_formation():
490
+ """
491
+ Standing Wave Formation and Properties
492
+
493
+ Scenario:
494
+ This template models the superposition of two identical waves traveling in
495
+ opposite directions, which creates a standing wave. It requires using a
496
+ trigonometric identity to derive the mathematical form of the standing wave
497
+ and then interpreting this form to find the locations of nodes (zero
498
+ amplitude) and antinodes (maximum amplitude).
499
+
500
+ Core Equations:
501
+ cos(a) + cos(b) = 2 * cos((a-b)/2) * cos((a+b)/2)
502
+ Standing Wave: y(x,t) = [2*A*cos(k*x)] * cos(omega*t)
503
+ Nodes: k*x = (n + 1/2)*pi
504
+ Antinodes: k*x = n*pi
505
+
506
+ Returns:
507
+ tuple: A tuple containing:
508
+ - str: A question asking to derive and analyze a standing wave.
509
+ - str: A step-by-step solution showing the derivation and calculations.
510
+ """
511
+ # 1. Parameterize the inputs
512
+ precision = 2
513
+ A = random.randint(5, 50)
514
+ omega = random.randint(100, 500)
515
+ k = round(random.uniform(1.0, 5.0), precision)
516
+
517
+ # 2. Generate the question string
518
+ y1_str = f"{A} * cos({omega}*t - {k}*x)"
519
+ y2_str = f"{A} * cos({omega}*t + {k}*x)"
520
+
521
+ question = (
522
+ f"Two waves of the same amplitude, frequency, and wavelength travel in opposite directions, given by:\n"
523
+ f" y1(x,t) = {y1_str}\n"
524
+ f" y2(x,t) = {y2_str}\n\n"
525
+ f"a) Find the superposition of these waves, y(x,t) = y1(x,t) + y2(x,t), and show that it represents a standing wave.\n"
526
+ f"b) Determine the locations of the first three nodes and antinodes for x >= 0."
527
+ )
528
+
529
+ # 3. Perform the calculations for the solution
530
+
531
+ # Standing wave amplitude
532
+ standing_wave_amplitude = 2 * A
533
+
534
+ # Wavelength
535
+ wavelength = (2 * math.pi) / k
536
+
537
+ # Node locations
538
+ nodes = [(n + 0.5) * math.pi / k for n in range(3)]
539
+
540
+ # Antinode locations
541
+ antinodes = [n * math.pi / k for n in range(3)]
542
+
543
+ # 4. Generate the solution string
544
+
545
+ standing_wave_eq = f"{standing_wave_amplitude} * cos({k}*x) * cos({omega}*t)"
546
+
547
+ solution = (
548
+ f"**Given:**\n"
549
+ f" Incident wave y1(x,t) = {y1_str}\n"
550
+ f" Reflected wave y2(x,t) = {y2_str}\n\n"
551
+
552
+ f"**Step 1:** Sum the two traveling waves using a trigonometric identity.\n"
553
+ f" We use the identity: cos(alpha) + cos(beta) = 2 * cos((alpha - beta)/2) * cos((alpha + beta)/2).\n"
554
+ f" Let alpha = {omega}*t - {k}*x and beta = {omega}*t + {k}*x.\n"
555
+ f" (alpha - beta)/2 = (({omega}*t - {k}*x) - ({omega}*t + {k}*x))/2 = -{k}*x\n"
556
+ f" (alpha + beta)/2 = (({omega}*t - {k}*x) + ({omega}*t + {k}*x))/2 = {omega}*t\n"
557
+ f" Substituting these into the identity and using cos(-z) = cos(z), we get:\n"
558
+ f" y(x,t) = {A} * [2 * cos(-{k}*x) * cos({omega}*t)] = {standing_wave_eq}\n"
559
+ f" This is the equation of a standing wave because the spatial dependence (cos({k}*x)) is separate from the time dependence (cos({omega}*t)).\n\n"
560
+
561
+ f"**Step 2:** Find the locations of the nodes.\n"
562
+ f" Nodes are points of zero amplitude, which occur when the spatial term is zero: cos({k}*x) = 0.\n"
563
+ f" This condition is met when k*x = (n + 1/2)*pi, for n = 0, 1, 2, ...\n"
564
+ f" Solving for x: x = (n + 1/2) * pi / k.\n"
565
+ f" - For n=0: x = (0.5 * pi) / {k} = {round(nodes[0], precision)}\n"
566
+ f" - For n=1: x = (1.5 * pi) / {k} = {round(nodes[1], precision)}\n"
567
+ f" - For n=2: x = (2.5 * pi) / {k} = {round(nodes[2], precision)}\n\n"
568
+
569
+ f"**Step 3:** Find the locations of the antinodes.\n"
570
+ f" Antinodes are points of maximum amplitude, which occur when |cos({k}*x)| = 1.\n"
571
+ f" This condition is met when k*x = n*pi, for n = 0, 1, 2, ...\n"
572
+ f" Solving for x: x = n * pi / k.\n"
573
+ f" - For n=0: x = (0 * pi) / {k} = {round(antinodes[0], precision)}\n"
574
+ f" - For n=1: x = (1 * pi) / {k} = {round(antinodes[1], precision)}\n"
575
+ f" - For n=2: x = (2 * pi) / {k} = {round(antinodes[2], precision)}\n\n"
576
+
577
+ f"**Answer:**\n"
578
+ f" - The standing wave equation is y(x,t) = {standing_wave_eq}.\n"
579
+ f" - The first three nodes are at x = {round(nodes[0], precision)}, {round(nodes[1], precision)}, and {round(nodes[2], precision)}.\n"
580
+ f" - The first three antinodes are at x = {round(antinodes[0], precision)}, {round(antinodes[1], precision)}, and {round(antinodes[2], precision)}."
581
+ )
582
+
583
+ return question, solution
584
+
585
+
586
+ def main():
587
+ """
588
+ Generate numerous instances of each waves and phasors template
589
+ with different random seeds and write the results to a JSONL file.
590
+ """
591
+ import json
592
+ import os
593
+
594
+ # Define the output path (Modify this path according to where you are running the code from)
595
+ output_file = "testset/electrical_engineering/electromagnetics_and_waves/waves_and_phasors.jsonl"
596
+
597
+ # Create the directory if it doesn't exist
598
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
599
+
600
+ # List of template functions with their ID and level
601
+ templates = [
602
+ (template_wave_parameters_basic, "wave_parameters_basic", "Easy"),
603
+ (template_time_to_phasor, "time_to_phasor", "Easy"),
604
+ (template_wave_equation_interpretation, "wave_equation_interpretation", "Intermediate"),
605
+ (template_phasor_addition, "phasor_addition", "Intermediate"),
606
+ (template_standing_wave_formation, "standing_wave_formation", "Advanced"),
607
+ ]
608
+
609
+ # List to store all generated problems
610
+ all_problems = []
611
+
612
+ # Generate problems for each template
613
+ for template_func, id_name, level in templates:
614
+ for _ in range(50):
615
+ # Generate a unique seed for each problem
616
+ seed = random.randint(1_000_000_000, 4_000_000_000)
617
+ random.seed(seed)
618
+
619
+ # Generate the problem and solution
620
+ question, solution = template_func()
621
+
622
+ # Create a JSON entry
623
+ problem_entry = {
624
+ "seed": seed,
625
+ "branch": "electrical_engineering",
626
+ "domain": "electromagnetics_and_waves",
627
+ "area": "waves_and_phasors",
628
+ "id": id_name,
629
+ "level": level,
630
+ "question": question,
631
+ "solution": solution
632
+ }
633
+
634
+ # Add to the list of problems
635
+ all_problems.append(problem_entry)
636
+
637
+ # Shuffle the problems to mix templates and levels
638
+ random.shuffle(all_problems)
639
+
640
+ # Write all problems to a .jsonl file
641
+ with open(output_file, "w") as file:
642
+ for problem in all_problems:
643
+ file.write(json.dumps(problem))
644
+ file.write("\n")
645
+
646
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
647
+
648
+
649
+ if __name__ == "__main__":
650
+ main()
data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/continuous_time_signals.cpython-311.pyc ADDED
Binary file (26.2 kB). View file
 
data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/discrete_time_signals.cpython-311.pyc ADDED
Binary file (39.3 kB). View file
 
data/templates/branches/electrical_engineering/signals_and_systems/continuous_time_signals.py ADDED
@@ -0,0 +1,621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ from fractions import Fraction
4
+ from data.templates.branches.electrical_engineering.constants import FREQUENCY_RANGE_HZ, AMPLITUDE_RANGE, PHASE_RANGE_DEG, PHASE_RANGE_RAD, OMEGA_MULTIPLIER_RANGE, SAMPLING_FREQ_RANGE_HZ, F0_RANGE_HZ, GAIN_K_RANGE, DELAY_N0_RANGE, DECIMATION_FACTOR_M_RANGE, OMEGA_DENOMINATOR_RANGE
5
+
6
+
7
+ # Template 1 (Easy)
8
+ def template_nyquist_rate_determination():
9
+ """
10
+ Nyquist Rate Determination
11
+
12
+ Scenario:
13
+ This template tests the fundamental understanding of the Nyquist-Shannon
14
+ sampling theorem. The user must identify the highest frequency component in a
15
+ continuous-time signal and use it to calculate the minimum sampling rate
16
+ required to prevent aliasing.
17
+
18
+ Core Equations:
19
+ F_nyquist = 2 * F_max
20
+
21
+ Returns:
22
+ tuple: A tuple containing:
23
+ - str: A question asking for the Nyquist rate of a given signal.
24
+ - str: A step-by-step solution.
25
+ """
26
+ # 1. Parameterize the inputs with random values
27
+
28
+ # Determine the number of sinusoidal components in the signal
29
+ num_components = random.choice([2, 3])
30
+
31
+ # Generate a set of unique, random frequencies to serve as the basis for the problem.
32
+ # Using random.sample ensures all generated frequencies are distinct.
33
+ frequency_pool = range(FREQUENCY_RANGE_HZ[0], FREQUENCY_RANGE_HZ[1])
34
+ frequencies_hz = sorted(random.sample(frequency_pool, num_components), reverse=True)
35
+
36
+ signal_terms = []
37
+ for freq in frequencies_hz:
38
+ # For each frequency, generate a random amplitude, phase, and function type (sin/cos)
39
+ amplitude = round(random.uniform(*AMPLITUDE_RANGE), 1)
40
+ phase_deg = random.randint(*PHASE_RANGE_DEG)
41
+ func_type = random.choice(['cos', 'sin'])
42
+
43
+ # Create the mathematical term as a string
44
+ # Example: "15.5 * cos(2*pi*750*t + 45 deg)"
45
+ term_str = f"{amplitude} * {func_type}(2*pi*{freq}*t + {phase_deg} deg)"
46
+ signal_terms.append(term_str)
47
+
48
+ # Join the individual terms with a " + " to form the full signal equation
49
+ signal_expression = " + ".join(signal_terms)
50
+
51
+ # 2. Perform the core calculation for the solution
52
+ # The highest frequency determines the Nyquist rate.
53
+ f_max = max(frequencies_hz)
54
+
55
+ # The Nyquist rate is twice the maximum frequency.
56
+ f_nyquist = 2 * f_max
57
+
58
+ # 3. Generate the question and solution strings
59
+ question = (
60
+ f"A continuous-time signal is defined as:\n"
61
+ f" x_c(t) = {signal_expression}\n\n"
62
+ f"What is the minimum sampling rate (Fs), also known as the Nyquist rate, "
63
+ f"required to sample this signal without loss of information?"
64
+ )
65
+
66
+ solution = (
67
+ f"**Given Signal:**\n"
68
+ f"x_c(t) = {signal_expression}\n\n"
69
+
70
+ f"**Step 1:** Identify Frequencies\n"
71
+ f"First, we identify all the unique frequency components in the signal's expression. "
72
+ f"By inspecting the '2*pi*F*t' part of each term, we find the frequencies are:\n"
73
+ f"Frequencies = {', '.join(map(str, frequencies_hz))} Hz.\n\n"
74
+
75
+ f"**Step 2:** Find Maximum Frequency\n"
76
+ f"The Nyquist rate is determined by the highest frequency component in the signal. "
77
+ f"We find the maximum value from the list of frequencies.\n"
78
+ f"F_max = max({', '.join(map(str, frequencies_hz))}) = {f_max} Hz.\n\n"
79
+
80
+ f"**Step 3:** Apply Nyquist Theorem\n"
81
+ f"The Nyquist-Shannon sampling theorem states that to avoid aliasing and be able "
82
+ f"to perfectly reconstruct a signal, the sampling rate (Fs) must be at least "
83
+ f"twice its highest frequency component (F_max).\n"
84
+ f"Fs_min = 2 * F_max.\n\n"
85
+
86
+ f"**Step 4:** Calculate the Nyquist Rate\n"
87
+ f"Using the maximum frequency found in Step 2, we calculate the Nyquist rate:\n"
88
+ f"FNyquist = 2 * {f_max} = {f_nyquist} Hz.\n\n"
89
+
90
+ f"**Answer:**\n"
91
+ f"The minimum sampling rate required to sample the signal without loss of "
92
+ f"information is {f_nyquist} Hz."
93
+ )
94
+
95
+ return question, solution
96
+
97
+
98
+ # Template 2 (Easy)
99
+ def template_continuous_to_discrete_conversion():
100
+ """
101
+ Continuous-to-Discrete Signal Conversion
102
+
103
+ Scenario:
104
+ This template tests the direct application of the sampling process. The user is
105
+ given a continuous-time sinusoid and a sampling rate and must derive the
106
+ corresponding discrete-time sequence by substituting t=nT. It reinforces the
107
+ relationship between continuous and discrete frequencies (omega = Omega * T).
108
+
109
+ Core Equations:
110
+ x[n] = x_c(nT)
111
+ T = 1 / Fs
112
+ omega = Omega * T
113
+
114
+ Returns:
115
+ tuple: A tuple containing:
116
+ - str: A question asking for the discrete-time representation of a signal.
117
+ - str: A step-by-step solution.
118
+ """
119
+ # 1. Parameterize the inputs with random values
120
+ amplitude = round(random.uniform(*AMPLITUDE_RANGE), 2)
121
+
122
+ # Generate a continuous-time angular frequency as an integer multiple of pi
123
+ omega_multiplier = random.randint(*OMEGA_MULTIPLIER_RANGE)
124
+ omega_continuous = omega_multiplier * math.pi
125
+ omega_continuous_str = f"{omega_multiplier}*pi"
126
+
127
+ # Randomly choose to express the phase in degrees or radians for variety
128
+ use_degrees = random.choice([True, False])
129
+ if use_degrees:
130
+ phi_deg = random.randint(*PHASE_RANGE_DEG)
131
+ phi_str = f"{phi_deg} deg"
132
+ else:
133
+ phi_rad = round(random.uniform(*PHASE_RANGE_RAD), 2)
134
+ phi_str = f"{phi_rad} rad"
135
+
136
+ # Generate a random sampling frequency
137
+ sampling_freq_hz = random.randint(*SAMPLING_FREQ_RANGE_HZ)
138
+
139
+ # Construct the full continuous-time signal expression for the question
140
+ signal_expression = f"{amplitude} * cos({omega_continuous_str}*t + {phi_str})"
141
+
142
+ # 2. Perform the core calculation for the solution
143
+ sampling_period = 1 / sampling_freq_hz
144
+
145
+ # The discrete-time angular frequency
146
+ omega_discrete = omega_continuous * sampling_period
147
+
148
+ # To create a clean, readable solution, simplify the fraction (multiplier / Fs)
149
+ omega_fraction = Fraction(omega_multiplier, sampling_freq_hz).limit_denominator()
150
+ num = omega_fraction.numerator
151
+ den = omega_fraction.denominator
152
+
153
+ # Format the omega string for the solution
154
+ if den == 1:
155
+ omega_discrete_str = f"{num}*pi" if num != 1 else "pi"
156
+ elif num == 1:
157
+ omega_discrete_str = f"pi/{den}"
158
+ else:
159
+ omega_discrete_str = f"({num}*pi)/{den}"
160
+
161
+ # 3. Generate the question and solution strings
162
+ question = (
163
+ f"A continuous-time signal is given by the expression:\n"
164
+ f"x_c(t) = {signal_expression}\n\n"
165
+ f"This signal is sampled at a frequency of {sampling_freq_hz} Hz. "
166
+ f"Determine the resulting discrete-time signal, x[n], and identify "
167
+ f"its discrete-time angular frequency, omega."
168
+ )
169
+
170
+ solution = (
171
+ f"**Given:**\n"
172
+ f"Continuous-time signal: x_c(t) = {signal_expression}\n"
173
+ f"Sampling frequency: Fs = {sampling_freq_hz} Hz\n\n"
174
+
175
+ f"**Step 1:** State the Sampling Rule\n"
176
+ f"A discrete-time signal x[n] is obtained by replacing the continuous time variable 't' "
177
+ f"with its discrete counterpart 'nT', where T is the sampling period. The rule is:\n"
178
+ f"x[n] = x_c(nT)\n\n"
179
+
180
+ f"**Step 2:** Calculate Sampling Period (T)\n"
181
+ f"The sampling period is the reciprocal of the sampling frequency.\n"
182
+ f"T = 1 / Fs = 1 / {sampling_freq_hz} seconds.\n\n"
183
+
184
+ f"**Step 3:** Substitute t = nT into the Equation\n"
185
+ f"We replace every 't' in the original expression with 'nT'.\n"
186
+ f"x[n] = {amplitude} * cos({omega_continuous_str}*(nT) + {phi_str})\n\n"
187
+
188
+ f"**Step 4:** Identify Discrete Frequency (omega)\n"
189
+ f"Rearrange the terms to match the standard form x[n] = A * cos(omega*n + phi). "
190
+ f"The discrete-time angular frequency, omega, is the coefficient of 'n'.\n"
191
+ f"omega = Omega * T\n"
192
+ f"omega = ({omega_continuous_str}) * (1 / {sampling_freq_hz})\n\n"
193
+
194
+ f"**Step 5:** Calculate and Conclude\n"
195
+ f"Now, we calculate the numerical value for omega and write the final expression.\n"
196
+ f"omega = ({omega_multiplier} / {sampling_freq_hz}) * pi = {omega_discrete_str} rad/sample\n"
197
+ f"The final discrete-time signal is:\n"
198
+ f"x[n] = {amplitude} * cos({omega_discrete_str}*n + {phi_str})\n\n"
199
+
200
+ f"**Answer:**\n"
201
+ f"The resulting discrete-time signal is x[n] = {amplitude} * cos({omega_discrete_str}*n + {phi_str}), "
202
+ f"and its discrete-time angular frequency is omega = {omega_discrete_str} rad/sample."
203
+ )
204
+
205
+ return question, solution
206
+
207
+
208
+ # Template 3 (Intermediate)
209
+ def template_aliased_frequency_identification():
210
+ """
211
+ Identifying Aliased Frequencies
212
+
213
+ Scenario:
214
+ This template assesses the ability to predict the outcome of undersampling (aliasing).
215
+ When a signal is sampled below its Nyquist rate, its frequency appears as a
216
+ lower "folded" frequency. This function generates a problem to calculate this
217
+ apparent frequency.
218
+
219
+ Core Equations:
220
+ Fa = abs(F0 - k * Fs), where k = round(F0 / Fs)
221
+
222
+ Returns:
223
+ tuple: A tuple containing:
224
+ - str: A question asking for the apparent (aliased) frequency.
225
+ - str: A step-by-step solution showing the calculation.
226
+ """
227
+ # 1. Parameterize the inputs with random values
228
+
229
+ # Generate a random original frequency for the continuous-time signal.
230
+ f0 = random.randint(*F0_RANGE_HZ)
231
+
232
+ # Calculate the corresponding Nyquist rate for this signal.
233
+ f_nyquist = 2 * f0
234
+
235
+ # To guarantee aliasing, generate a sampling frequency (Fs) that is strictly
236
+ # less than the Nyquist rate. We set the range to be between 50% and 95% of
237
+ # the Nyquist rate to create a non-trivial undersampling problem.
238
+ fs = random.randint(int(0.5 * f_nyquist), int(0.95 * f_nyquist))
239
+
240
+ # 2. Perform the core calculation for the solution
241
+
242
+ # Find the integer multiple 'k' which represents the nearest multiple of the
243
+ # sampling frequency to the original frequency.
244
+ k = round(f0 / fs)
245
+
246
+ # Calculate the aliased frequency (Fa) using the folding formula.
247
+ fa = abs(f0 - k * fs)
248
+
249
+ # Standardize the precision for all final outputs for consistency.
250
+ precision = 2
251
+
252
+ # 3. Generate the question and solution strings
253
+ question = (
254
+ f"A continuous-time signal x_c(t) = cos(2*pi*{f0}*t) is sampled at a rate "
255
+ f"of {fs} samples per second.\n\n"
256
+ f"Since this rate is below the Nyquist rate, aliasing occurs. What is the "
257
+ f"apparent frequency, Fa, of the resulting discrete-time signal? "
258
+ f"(The apparent frequency must be in the range 0 <= Fa <= {fs/2} Hz)."
259
+ )
260
+
261
+ solution = (
262
+ f"**Given:**\n"
263
+ f"Original Frequency (F0): {f0} Hz\n"
264
+ f"Sampling Frequency (Fs): {fs} Hz\n\n"
265
+
266
+ f"**Step 1:** State the Condition\n"
267
+ f"The Nyquist rate required to sample this signal without aliasing is "
268
+ f"2 * F0 = 2 * {f0} = {f_nyquist} Hz. "
269
+ f"Since the sampling frequency Fs = {fs} Hz is less than {f_nyquist} Hz, "
270
+ f"the original frequency will be aliased.\n\n"
271
+
272
+ f"**Step 2:** Explain Frequency Folding\n"
273
+ f"When aliasing occurs, the perceived frequency (Fa) is the absolute "
274
+ f"difference between the original frequency and the nearest integer multiple "
275
+ f"of the sampling frequency. The formula is:\n"
276
+ f"Fa = abs(F0 - k * Fs), where 'k' is an integer.\n\n"
277
+
278
+ f"**Step 3:** Find the Correct Multiple (k)\n"
279
+ f"We find the integer 'k' that 'folds' F0 into the principal frequency "
280
+ f"range [0, Fs/2]. This is found by rounding the ratio of F0 to Fs.\n"
281
+ f"k = round(F0 / Fs) = round({f0} / {fs}) = round({round(f0/fs, 4)}) = {k}.\n\n"
282
+
283
+ f"**Step 4:** Calculate Aliased Frequency (Fa)\n"
284
+ f"Now, we compute Fa using the value of k found in the previous step.\n"
285
+ f"Fa = abs({f0} - {k} * {fs})\n"
286
+ f"Fa = abs({f0} - {k * fs})\n"
287
+ f"Fa = {round(fa, precision)} Hz.\n\n"
288
+
289
+ f"**Answer:**\n"
290
+ f"The apparent (aliased) frequency of the resulting signal is {round(fa, precision)} Hz."
291
+ )
292
+
293
+ return question, solution
294
+
295
+
296
+ # Template 4 (Intermediate)
297
+ def template_cd_dc_system_analysis():
298
+ """
299
+ C/D and D/C System Analysis
300
+
301
+ Scenario:
302
+ This template models a full signal processing chain: a continuous-time signal is
303
+ sampled (C/D), processed by a simple discrete-time system (a gain and delay),
304
+ and perfectly reconstructed back to continuous-time (D/C). It tests the
305
+ understanding of how each stage affects the final signal.
306
+
307
+ Core Equations:
308
+ x[n] = x_c(nT)
309
+ y_c(t) is the ideal reconstruction of y[n].
310
+
311
+ Returns:
312
+ tuple: A tuple containing:
313
+ - str: A question describing the system and asking for the final output.
314
+ - str: A step-by-step solution.
315
+ """
316
+ # 1. Parameterize the inputs with random values
317
+
318
+ # --- Signal Parameters ---
319
+ amplitude = round(random.uniform(*AMPLITUDE_RANGE), 2)
320
+ omega_multiplier = random.randint(*OMEGA_MULTIPLIER_RANGE)
321
+ omega_continuous = omega_multiplier * math.pi
322
+ omega_continuous_str = f"{omega_multiplier}*pi"
323
+
324
+ # --- Sampling Parameters ---
325
+ # Ensure the Nyquist criterion is satisfied by a wide margin.
326
+ # F0 = Omega0 / (2*pi) = (multiplier*pi) / (2*pi) = multiplier / 2.
327
+ # F_nyquist = 2 * F0 = multiplier.
328
+ f_nyquist = omega_multiplier
329
+ # Sample at 3 to 8 times the Nyquist rate to make it a clear "ideal" case.
330
+ sampling_factor = random.randint(3, 8)
331
+ sampling_freq_hz = f_nyquist * sampling_factor
332
+ sampling_period = 1 / sampling_freq_hz
333
+
334
+ # --- System Parameters ---
335
+ gain_k = round(random.uniform(*GAIN_K_RANGE), 2)
336
+ delay_n0 = random.randint(*DELAY_N0_RANGE)
337
+
338
+ # 2. Perform the core calculations for the solution
339
+
340
+ # The final output amplitude is the original amplitude scaled by the system gain.
341
+ output_amplitude = round(amplitude * gain_k, 2)
342
+
343
+ # The total time delay is the discrete sample delay multiplied by the sampling period.
344
+ time_delay_sec = delay_n0 * sampling_period
345
+
346
+ # 3. Generate the question and solution strings
347
+
348
+ # Define the strings for the mathematical expressions
349
+ x_c_t_expr = f"{amplitude} * cos({omega_continuous_str}*t)"
350
+ system_expr = f"y[n] = {gain_k} * x[n - {delay_n0}]"
351
+
352
+ question = (
353
+ f"A continuous-time signal is given by:\n"
354
+ f"x_c(t) = {x_c_t_expr}\n\n"
355
+ f"The signal is sampled with a period T = {sampling_period:.2e} s to produce the "
356
+ f"discrete-time sequence x[n]. This sequence is then processed by a system "
357
+ f"defined by the equation:\n"
358
+ f"{system_expr}\n\n"
359
+ f"If the resulting output sequence, y[n], is converted back to a continuous-time "
360
+ f"signal, y_c(t), using an ideal D/C converter, what is the expression for y_c(t)?"
361
+ )
362
+
363
+ solution = (
364
+ f"**Given:**\n"
365
+ f"Continuous-time signal: x_c(t) = {x_c_t_expr}\n"
366
+ f"Sampling period: T = {sampling_period:.2e} s\n"
367
+ f"Discrete-time system: {system_expr}\n\n"
368
+
369
+ f"**Step 1:** Continuous-to-Discrete (C/D) Conversion\n"
370
+ f"We find the discrete-time signal x[n] by substituting t = nT into x_c(t).\n"
371
+ f"x[n] = x_c(nT) = {amplitude} * cos({omega_continuous_str} * nT)\n\n"
372
+
373
+ f"**Step 2:** Discrete-Time Processing\n"
374
+ f"Next, we apply the system's equation to x[n]. This involves replacing "
375
+ f"x[n] with the expression from Step 1 and shifting it by n0 = {delay_n0} samples.\n"
376
+ f"y[n] = {gain_k} * x[n - {delay_n0}]\n"
377
+ f"y[n] = {gain_k} * ({amplitude} * cos({omega_continuous_str} * (n - {delay_n0})T))\n"
378
+ f"y[n] = {output_amplitude} * cos({omega_continuous_str}*T*(n - {delay_n0}))\n\n"
379
+
380
+ f"**Step 3: ** Discrete-to-Continuous (D/C) Conversion*\n"
381
+ f"An ideal D/C converter maps the discrete-time signal back to a continuous-time "
382
+ f"sinusoid with the original frequency Omega_0 = {omega_continuous_str}. The discrete "
383
+ f"time 'n' is mapped back to continuous time 't' via the relation t = nT.\n"
384
+ f"y_c(t) = {output_amplitude} * cos({omega_continuous_str} * (t - {delay_n0}T))\n"
385
+ f"The term {delay_n0}*T represents a time delay. Let's calculate its value:\n"
386
+ f"Time Delay = {delay_n0} * T = {delay_n0} * {sampling_period:.2e} = {time_delay_sec:.2e} s\n"
387
+ f"Substituting this back, we get the final expression:\n"
388
+ f"y_c(t) = {output_amplitude} * cos({omega_continuous_str}*(t - {time_delay_sec:.2e}))\n\n"
389
+
390
+ f"**Step 4:** Interpret the Result\n"
391
+ f"The output signal y_c(t) is a modified version of the input signal x_c(t). "
392
+ f"Its amplitude has been scaled by a factor of K = {gain_k}, and it has been "
393
+ f"time-delayed by {time_delay_sec:.2e} seconds.\n\n"
394
+
395
+ f"**Answer:**\n"
396
+ f"The final continuous-time output signal is y_c(t) = {output_amplitude} * cos({omega_continuous_str}*(t - {time_delay_sec:.2e}))."
397
+ )
398
+
399
+ return question, solution
400
+
401
+
402
+ # Template 5 (Advanced)
403
+ def template_decimation_aliasing_analysis():
404
+ """
405
+ Decimation (Downsampling) with Aliasing Analysis
406
+
407
+ Scenario:
408
+ This template tests the understanding of decimation and its potential to cause
409
+ aliasing if the signal is not properly band-limited. The generated problem may
410
+ or may not result in aliasing, requiring a full analysis.
411
+
412
+ Core Equations:
413
+ y[n] = x[Mn]
414
+ Aliasing is avoided if omega_0 < pi / M.
415
+ omega_aliased = abs(omega_prime - 2*pi*k), where omega_prime = omega_0 * M.
416
+
417
+ Returns:
418
+ tuple: A tuple containing:
419
+ - str: A multi-part question about the downsampled signal.
420
+ - str: A step-by-step solution with conditional aliasing analysis.
421
+ """
422
+ # 1. Parameterize the inputs with random values
423
+ M = random.randint(*DECIMATION_FACTOR_M_RANGE)
424
+
425
+ # Randomly decide whether to create a problem that results in aliasing or not.
426
+ will_alias = random.choice([True, False])
427
+
428
+ # Generate an initial frequency omega_0 = (num/den)*pi based on the aliasing choice.
429
+ # The condition for NO aliasing is omega_0 < pi/M, which means num/den < 1/M.
430
+ denominator = random.randint(*OMEGA_DENOMINATOR_RANGE)
431
+
432
+ if not will_alias:
433
+ # We need num/den < 1/M => num < den/M.
434
+ max_numerator = math.floor(denominator / M) - 1
435
+ if max_numerator < 1: max_numerator = 1 # Ensure at least one valid choice
436
+ numerator = random.randint(1, max_numerator)
437
+ else:
438
+ # We need num/den > 1/M => num > den/M.
439
+ min_numerator = math.ceil(denominator / M)
440
+ if min_numerator >= denominator: min_numerator = denominator - 1 # Ensure valid range
441
+ numerator = random.randint(min_numerator, denominator - 1)
442
+
443
+ # The original discrete-time frequency
444
+ omega_0 = (numerator / denominator) * math.pi
445
+
446
+ # --- In-line formatting for omega_0_str ---
447
+ frac_0 = Fraction(numerator, denominator)
448
+ n0, d0 = frac_0.numerator, frac_0.denominator
449
+ if d0 == 1:
450
+ omega_0_str = f"{n0}*pi" if n0 != 1 else "pi"
451
+ elif n0 == 1:
452
+ omega_0_str = f"pi/{d0}"
453
+ else:
454
+ omega_0_str = f"({n0}*pi)/{d0}"
455
+
456
+ # 2. Perform the core calculations for the solution
457
+ aliasing_boundary = math.pi / M
458
+
459
+ # --- In-line formatting for aliasing_boundary_str ---
460
+ aliasing_boundary_str = f"pi/{M}"
461
+
462
+ # The new frequency after substitution before considering the principal range.
463
+ omega_prime = omega_0 * M
464
+
465
+ # --- In-line formatting for omega_prime_str ---
466
+ frac_prime = Fraction(numerator * M, denominator)
467
+ np, dp = frac_prime.numerator, frac_prime.denominator
468
+ if dp == 1:
469
+ omega_prime_str = f"{np}*pi" if np != 1 else "pi"
470
+ elif np == 1:
471
+ omega_prime_str = f"pi/{dp}"
472
+ else:
473
+ omega_prime_str = f"({np}*pi)/{dp}"
474
+
475
+ # Calculate the final perceived frequency.
476
+ if not will_alias:
477
+ final_omega_str = omega_prime_str
478
+ k_val = 0 # No folding needed
479
+ else:
480
+ # The frequency must be folded back into the principal range [-pi, pi].
481
+ k_val = round(omega_prime / (2 * math.pi))
482
+ final_omega = abs(omega_prime - 2 * math.pi * k_val)
483
+
484
+ # --- In-line formatting for final_omega_str ---
485
+ frac_final = Fraction(final_omega / math.pi).limit_denominator(100)
486
+ nf, df = frac_final.numerator, frac_final.denominator
487
+ if df == 1:
488
+ final_omega_str = f"{nf}*pi" if nf != 1 else "pi"
489
+ elif nf == 0:
490
+ final_omega_str = "0"
491
+ elif nf == 1:
492
+ final_omega_str = f"pi/{df}"
493
+ else:
494
+ final_omega_str = f"({nf}*pi)/{df}"
495
+
496
+ # 3. Generate the question and solution strings
497
+ question = (
498
+ f"A discrete-time signal is given by x[n] = cos({omega_0_str}*n).\n"
499
+ f"This signal is downsampled by a factor of M = {M} to produce the signal y[n] = x[Mn].\n\n"
500
+ f"a) Find an expression for the output signal y[n] in the form cos(omega'*n).\n"
501
+ f"b) The condition to avoid aliasing during downsampling is omega_0 < {aliasing_boundary_str}. Based on the given values, did aliasing occur?\n"
502
+ f"c) What is the final perceived angular frequency, omega_a, in the signal y[n]? (The frequency must be in the range 0 <= omega_a <= pi)."
503
+ )
504
+
505
+ # Build the solution string piece by piece
506
+ solution = (
507
+ f"**Given:**\n"
508
+ f"Signal: x[n] = cos({omega_0_str}*n)\n"
509
+ f"Downsampling Factor: M = {M}\n\n"
510
+
511
+ f"**Step 1:** Find the Output Signal Expression\n"
512
+ f"We substitute 'n' with 'Mn' in the original expression for x[n]:\n"
513
+ f" y[n] = x[Mn] = cos({omega_0_str} * (Mn)) = cos(({omega_0_str} * {M}) * n)\n"
514
+ f"The new angular frequency, omega', is:\n"
515
+ f" omega' = {omega_0_str} * {M} = {omega_prime_str}\n"
516
+ f"So, the expression is y[n] = cos({omega_prime_str}*n).\n\n"
517
+
518
+ f"**Step 2:** Check for Aliasing\n"
519
+ f"Aliasing is avoided if the original frequency omega_0 is less than pi/M.\n"
520
+ f"Aliasing Boundary = pi / M = {aliasing_boundary_str} approx {round(aliasing_boundary, 3)}\n"
521
+ f"Original Frequency = {omega_0_str} approx {round(omega_0, 3)}\n"
522
+ f"Comparing the two, we find that {omega_0_str} is {'NOT less' if will_alias else 'less'} than {aliasing_boundary_str}. "
523
+ f"Therefore, aliasing **{'occurred' if will_alias else 'did not occur'}**.\n\n"
524
+
525
+ f"**Step 3:** Find the Final Perceived Frequency (omega_a)\n"
526
+ )
527
+
528
+ if not will_alias:
529
+ solution += (
530
+ f"Since no aliasing occurred, the frequency omega' = {omega_prime_str} is already in the principal range [0, pi].\n"
531
+ f"Therefore, the final perceived frequency is the same.\n"
532
+ f"omega_a = {final_omega_str}"
533
+ )
534
+ else:
535
+ # For clarity, re-calculate final_omega inside the solution string
536
+ final_omega_val = abs(omega_prime - 2 * math.pi * k_val)
537
+ solution += (
538
+ f"Because aliasing occurred, the frequency omega' = {omega_prime_str} is outside the principal range [0, pi] and must be 'folded' back.\n"
539
+ f"We find an integer 'k' such that omega_a = abs(omega' - 2*pi*k) is in the range [0, pi].\n"
540
+ f"k = round(omega' / (2*pi)) = round({omega_prime/math.pi:.2f}*pi / (2*pi)) = round({omega_prime/(2*math.pi):.2f}) = {k_val}\n"
541
+ f"Now we calculate omega_a:\n"
542
+ f"omega_a = abs({omega_prime_str} - 2*pi*{k_val})\n"
543
+ f"omega_a = abs({round(omega_prime, 3)} - {round(2*math.pi*k_val, 3)}) = {round(final_omega_val, 3)}\n"
544
+ f"In terms of pi, this is omega_a = {final_omega_str}.\n\n"
545
+ )
546
+
547
+ solution += (
548
+ f"\n\n**Answer:**\n"
549
+ f"a) The output signal is y[n] = cos({omega_prime_str}*n).\n"
550
+ f"b) Aliasing **{'did' if will_alias else 'did not'}** occur.\n"
551
+ f"c) The final perceived frequency is omega_a = {final_omega_str}."
552
+ )
553
+
554
+ return question, solution
555
+
556
+
557
+ def main():
558
+ """
559
+ Generate numerous instances of each continuous time signals template
560
+ with different random seeds and write the results to a JSONL file.
561
+ """
562
+ import json
563
+ import os
564
+
565
+ # Define the output path (Modify this path according to where you are running the code from)
566
+ output_file = "testset/electrical_engineering/signals_and_systems/continuous_time_signals.jsonl"
567
+
568
+ # Create the directory if it doesn't exist
569
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
570
+
571
+ # List of template functions with their ID and level
572
+ templates = [
573
+ (template_nyquist_rate_determination, "nyquist_rate_determination", "Easy"),
574
+ (template_continuous_to_discrete_conversion, "continuous_to_discrete_conversion", "Easy"),
575
+ (template_aliased_frequency_identification, "aliased_frequency_identification", "Intermediate"),
576
+ (template_cd_dc_system_analysis, "cd_dc_system_analysis", "Intermediate"),
577
+ (template_decimation_aliasing_analysis, "decimation_aliasing_analysis", "Advanced"),
578
+ ]
579
+
580
+ # List to store all generated problems
581
+ all_problems = []
582
+
583
+ # Generate problems for each template
584
+ for template_func, id_name, level in templates:
585
+ for _ in range(50):
586
+ # Generate a unique seed for each problem
587
+ seed = random.randint(1_000_000_000, 4_000_000_000)
588
+ random.seed(seed)
589
+
590
+ # Generate the problem and solution
591
+ question, solution = template_func()
592
+
593
+ # Create a JSON entry
594
+ problem_entry = {
595
+ "seed": seed,
596
+ "branch": "electrical_engineering",
597
+ "domain": "signals_and_systems",
598
+ "area": "continuous_time_signals",
599
+ "id": id_name,
600
+ "level": level,
601
+ "question": question,
602
+ "solution": solution
603
+ }
604
+
605
+ # Add to the list of problems
606
+ all_problems.append(problem_entry)
607
+
608
+ # Shuffle the problems to mix templates and levels
609
+ random.shuffle(all_problems)
610
+
611
+ # Write all problems to a .jsonl file
612
+ with open(output_file, "w") as file:
613
+ for problem in all_problems:
614
+ file.write(json.dumps(problem))
615
+ file.write("\n")
616
+
617
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
618
+
619
+
620
+ if __name__ == "__main__":
621
+ main()
data/templates/branches/electrical_engineering/signals_and_systems/discrete_time_signals.py ADDED
@@ -0,0 +1,817 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+
4
+
5
+ # Template 1 (Easy)
6
+ def template_signal_operations():
7
+ """
8
+ Signal Operations: Shifting and Reversal
9
+
10
+ Scenario:
11
+ This template tests the fundamental understanding of transformations on the
12
+ independent variable (time index 'n'). It requires applying a time-shift
13
+ (x[n-n0]) or a time-reversal (x[-n]) to a given finite-length sequence.
14
+ This is a core skill for understanding more complex operations like convolution.
15
+
16
+ Core Equations:
17
+ 1. Time-Shift: y[n] = x[n - n0]
18
+ 2. Time-Reversal: z[n] = x[-n]
19
+
20
+ Returns:
21
+ tuple: A tuple containing:
22
+ - str: A question asking to perform a time-shift or time-reversal on a sequence.
23
+ - str: A step-by-step solution showing the transformation of the sequence.
24
+ """
25
+ # 1. Parameterize the inputs with random values
26
+
27
+ # Generate a sequence x[n] as a dictionary {index: value}
28
+ seq_length = random.randint(4, 7)
29
+ # Ensure the origin (n=0) is not at the absolute ends of the sequence
30
+ origin_position = random.randint(1, seq_length - 2)
31
+ start_index = -origin_position
32
+
33
+ x_n = {start_index + i: random.randint(-10, 10) for i in range(seq_length)}
34
+
35
+ # Choose the operation
36
+ operation = random.choice(['shift', 'reversal'])
37
+
38
+ # 2. Perform the core calculation and generate explanatory text
39
+
40
+ if operation == 'shift':
41
+ # Select a non-zero shift amount
42
+ n0 = random.choice([-3, -2, -1, 1, 2, 3])
43
+ output_var = "y"
44
+
45
+ # Core calculation: Shift the keys of the dictionary
46
+ result_n = {k + n0: v for k, v in x_n.items()}
47
+
48
+ # Explanations for the solution
49
+ op_str_symbolic = f"x[n - ({n0})]" if n0 < 0 else f"x[n - {n0}]"
50
+
51
+ direction = "left" if n0 < 0 else "right"
52
+ explanation = (
53
+ f"The operation is a time shift, {output_var}[n] = {op_str_symbolic}.\n"
54
+ f"This corresponds to a shift of the sequence to the {direction} by {abs(n0)} sample(s).\n"
55
+ f"The value at any original index 'k' is moved to a new index 'k + {n0}'."
56
+ )
57
+
58
+ else: # operation == 'reversal'
59
+ n0 = 0 # Not used, but needed for variable scope
60
+ output_var = "z"
61
+
62
+ # Core calculation: Negate the keys of the dictionary
63
+ result_n = {-k: v for k, v in x_n.items()}
64
+
65
+ # Explanations for the solution
66
+ op_str_symbolic = "x[-n]"
67
+ explanation = (
68
+ f"The operation is a time reversal, {output_var}[n] = {op_str_symbolic}.\n"
69
+ f"This corresponds to flipping the sequence about the origin (n=0).\n"
70
+ f"The value at any original index 'k' is moved to a new index '-k'."
71
+ )
72
+
73
+ # Inlined logic to format the input sequence x_n into a string
74
+ if not x_n:
75
+ x_n_str = "{}"
76
+ else:
77
+ min_idx = min(x_n.keys())
78
+ max_idx = max(x_n.keys())
79
+ parts = []
80
+ for i in range(min_idx, max_idx + 1):
81
+ val = x_n.get(i, 0)
82
+ if i == 0:
83
+ parts.append(f"*{val}*") # Asterisk denotes the origin n=0
84
+ else:
85
+ parts.append(str(val))
86
+ x_n_str = f"{{{', '.join(parts)}}}"
87
+
88
+ # Inlined logic to format the result sequence into a string
89
+ if not result_n:
90
+ result_n_str = "{}"
91
+ else:
92
+ min_idx = min(result_n.keys())
93
+ max_idx = max(result_n.keys())
94
+ parts = []
95
+ for i in range(min_idx, max_idx + 1):
96
+ val = result_n.get(i, 0)
97
+ if i == 0:
98
+ parts.append(f"*{val}*")
99
+ else:
100
+ parts.append(str(val))
101
+ result_n_str = f"{{{', '.join(parts)}}}"
102
+
103
+ # 3. Generate the question and solution strings
104
+
105
+ question = (
106
+ f"A discrete-time signal is defined by the sequence:\n"
107
+ f"x[n] = {x_n_str}\n\n"
108
+ f"Determine the resulting sequence, {output_var}[n], after applying the following transformation:\n"
109
+ f"{output_var}[n] = {op_str_symbolic}"
110
+ )
111
+
112
+ # Inlined logic to build the transformation table for the solution
113
+ table_header = f"""| {'Original Index (k)':<20} | {'Original Value x[k]':<22} | {"New Index (k')":<18} | {"New Value y[k')":<20} |"""
114
+ table_separator = "-" * len(table_header)
115
+
116
+ table_rows_list = []
117
+ for k in sorted(x_n.keys()):
118
+ val = x_n[k]
119
+ if operation == 'shift':
120
+ new_k = k + n0
121
+ else: # reversal
122
+ new_k = -k
123
+ table_rows_list.append(f"| {k:<20} | {val:<22} | {new_k:<18} | {val:<20} |")
124
+
125
+ # Combine all parts of the table with newlines
126
+ transformation_table = f"{table_header}\n{table_separator}\n" + "\n".join(table_rows_list)
127
+
128
+ solution = (
129
+ f"**Given:**\n"
130
+ f"The original sequence is x[n] = {x_n_str}.\n"
131
+ f"The operation is {output_var}[n] = {op_str_symbolic}.\n\n"
132
+
133
+ f"**Step 1:** Understand the Transformation\n"
134
+ f"{explanation}\n\n"
135
+
136
+ f"**Step 2:** Apply the Transformation to Each Index\n"
137
+ f"We can create a table to track where each value moves:\n\n"
138
+ f"{transformation_table}\n\n"
139
+
140
+ f"**Step 3:** Construct the Final Sequence\n"
141
+ f"By collecting the values at their new indices, we get the final sequence.\n"
142
+ f"Remember that any index not explicitly calculated has a value of 0.\n\n"
143
+
144
+ f"**Answer:**\n"
145
+ f"The resulting sequence is {output_var}[n] = {result_n_str}"
146
+ )
147
+
148
+ return question, solution
149
+
150
+
151
+ # Template 2 (Easy)
152
+ def template_system_properties_memory_causality():
153
+ """
154
+ System Properties: Memory and Causality
155
+
156
+ Scenario:
157
+ This template tests the ability to analyze a discrete-time system's
158
+ input-output equation to determine two fundamental properties: whether it
159
+ is memoryless and whether it is causal. This requires applying the formal
160
+ definitions of these properties to the given equation.
161
+
162
+ Core Definitions:
163
+ 1. Memoryless: Output y[n] depends only on the current input x[n].
164
+ 2. Causal: Output y[n] depends only on present and past inputs x[k], where k <= n.
165
+
166
+ Returns:
167
+ tuple: A tuple containing:
168
+ - str: A question asking to determine if a system is memoryless and causal.
169
+ - str: A step-by-step solution analyzing each property with justifications.
170
+ """
171
+ # 1. Parameterize the inputs by generating a random system type and equation
172
+
173
+ system_type = random.choice(['memoryless_causal', 'memory_causal', 'memory_noncausal'])
174
+
175
+ is_memoryless = False
176
+ is_causal = False
177
+ equation_str = ""
178
+ memory_reason = ""
179
+ causality_reason = ""
180
+
181
+ if system_type == 'memoryless_causal':
182
+ is_memoryless = True
183
+ is_causal = True
184
+
185
+ form = random.choice(['power', 'affine', 'scaled_by_n'])
186
+ if form == 'power':
187
+ power = random.randint(2, 3)
188
+ equation_str = f"(x[n])**{power}"
189
+ memory_reason = f"The output y[n] is the input x[n] raised to the power of {power} at the exact same time 'n'. The system does not need to store any other input values."
190
+ elif form == 'affine':
191
+ gain = random.randint(2, 9)
192
+ offset = random.randint(1, 10)
193
+ equation_str = f"{gain} * x[n] + {offset}"
194
+ memory_reason = f"The output y[n] at time 'n' is a scaled and shifted version of the input x[n] at the exact same time 'n'. No past or future values of the input are needed."
195
+ else: # scaled_by_n
196
+ equation_str = f"n * x[n]"
197
+ memory_reason = "The output y[n] depends on the input x[n] at the same time 'n', scaled by the time index 'n' itself. The time index 'n' is not an input signal value that needs to be stored."
198
+
199
+ causality_reason = "The output y[n] depends only on the input at the present time 'n'. Since no future inputs (k > n) are required, the system is causal."
200
+
201
+ elif system_type == 'memory_causal':
202
+ is_memoryless = False
203
+ is_causal = True
204
+
205
+ delay = random.randint(1, 4)
206
+ form = random.choice(['simple_delay', 'with_present_term'])
207
+
208
+ if form == 'simple_delay':
209
+ equation_str = f"x[n - {delay}]"
210
+ memory_reason = f"To compute the output y[n], the system needs to know the input from {delay} time step(s) in the past, x[n - {delay}]. Therefore, the system must have memory."
211
+ else: # with_present_term
212
+ equation_str = f"x[n] + x[n - {delay}]"
213
+ memory_reason = f"The output y[n] depends on both the current input x[n] and a past input x[n - {delay}]. The system must store or 'remember' the value of x[n - {delay}] to calculate the current output."
214
+
215
+ causality_reason = f"The output y[n] depends on the present input and/or past inputs (up to time n - {delay}). Since it does not depend on any future inputs, the system is causal."
216
+
217
+ else: # system_type == 'memory_noncausal'
218
+ is_memoryless = False
219
+ is_causal = False
220
+
221
+ advance = random.randint(1, 4)
222
+ form = random.choice(['simple_advance', 'with_past_term'])
223
+
224
+ if form == 'simple_advance':
225
+ equation_str = f"x[n + {advance}]"
226
+ causality_reason = f"The output y[n] depends on the input at a future time, x[n + {advance}]. To calculate y[n], the system must know what the input will be {advance} time step(s) in the future. This violates the definition of causality."
227
+ else: # with_past_term
228
+ delay = random.randint(1, 2)
229
+ equation_str = f"x[n - {delay}] + x[n + {advance}]"
230
+ causality_reason = f"The output y[n] depends on a future input, x[n + {advance}]. Even though it also depends on a past input, the dependency on a future value makes the system non-causal."
231
+
232
+ memory_reason = "The output y[n] depends on input values at times other than 'n'. Therefore, the system is not memoryless."
233
+
234
+ # 2. Generate the question and solution strings
235
+
236
+ question = (
237
+ f"A discrete-time system is described by the input-output equation:\n"
238
+ f"y[n] = {equation_str}\n\n"
239
+ f"Determine if the system is:\n"
240
+ f"a) Memoryless\n"
241
+ f"b) Causal\n\n"
242
+ f"Justify your answers based on the definitions of these properties."
243
+ )
244
+
245
+ solution = (
246
+ f"**Given:**\n"
247
+ f"The system's input-output equation is y[n] = {equation_str}.\n\n"
248
+
249
+ f"**Step 1:** Analyze the Memoryless Property\n"
250
+ f"**Definition:** A system is **memoryless** if its output at any time 'n' depends *only* on the input at the same time 'n'.\n"
251
+ f"**Analysis:** {memory_reason}\n"
252
+ f"**Conclusion:** Based on the analysis, the system is **{'memoryless' if is_memoryless else 'not memoryless'}**.\n\n"
253
+
254
+ f"**Step 2:** Analyze the Causality Property\n"
255
+ f"**Definition:** A system is **causal** if its output at any time 'n' depends *only* on the input at the present time 'n' and past times (i.e., on x[k] for k <= n).\n"
256
+ f"**Analysis:** {causality_reason}\n"
257
+ f"**Conclusion:** Based on the analysis, the system is **{'causal' if is_causal else 'not causal'}**.\n\n"
258
+
259
+ f"**Answer:**\n"
260
+ f"a) Memoryless: **{'Yes' if is_memoryless else 'No'}**\n"
261
+ f"b) Causal: **{'Yes' if is_causal else 'No'}**"
262
+ )
263
+
264
+ return question, solution
265
+
266
+
267
+ # Template 3 (Intermediate)
268
+ def template_finite_convolution():
269
+ """
270
+ Convolution of Finite-Length Sequences
271
+
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 = []
309
+ for i in range(min_idx_x, max_idx_x + 1):
310
+ val = x_n.get(i, 0)
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 = []
318
+ for i in range(min_idx_h, max_idx_h + 1):
319
+ val = h_n.get(i, 0)
320
+ h_parts.append(f"*{val}*" if i == 0 else str(val))
321
+ h_n_str = f"{{{', '.join(h_parts)}}}"
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
+
332
+ for n in range(y_start_idx, y_end_idx + 1):
333
+ current_sum = 0
334
+ for k in all_k_indices:
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
+
342
+ # 3. Generate the question and solution strings
343
+
344
+ question = (
345
+ f"An LTI system has an impulse response h[n] = {h_n_str}.\n\n"
346
+ f"Determine the system's output, y[n] = x[n] * h[n], for the input x[n] = {x_n_str}."
347
+ )
348
+
349
+ # Build the detailed calculation steps for the solution
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
+
359
+ for n in y_indices_to_show:
360
+ step_str = f"For n = {n}:\n"
361
+ step_str += f"y[{n}] = sum( x[k] * h[{n}-k] )\n"
362
+
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 = []
391
+ for i in range(min_idx_y, max_idx_y + 1):
392
+ val = y_n.get(i, 0)
393
+ y_parts.append(f"*{val}*" if i == 0 else str(val))
394
+ y_n_str = f"{{{', '.join(y_parts)}}}"
395
+
396
+ y_values_list = [f" y[{n}] = {y_n.get(n, 0)}" for n in range(y_start_idx, y_end_idx + 1)]
397
+ y_values_str = "\n".join(y_values_list)
398
+
399
+ calculation_steps_str = "\n".join(calculation_steps)
400
+
401
+ solution = (
402
+ f"**Given:**\n"
403
+ f"Input Signal: x[n] = {x_n_str}\n"
404
+ f"Impulse Response: h[n] = {h_n_str}\n\n"
405
+
406
+ f"**Step 1:** State the Convolution Formula\n"
407
+ f"The output y[n] of an LTI system is the convolution of the input x[n] with the impulse response h[n]. The convolution sum is defined as:\n"
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"
420
+ f"The complete output sequence is y[n] = {y_n_str}"
421
+ )
422
+
423
+ return question, solution
424
+
425
+
426
+ # Template 4 (Intermediate)
427
+ def template_system_property_linearity():
428
+ """
429
+ System Properties: Linearity
430
+
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]}
439
+ 2. Homogeneity: T{a * x[n]} = a * T{x[n]}
440
+
441
+ Returns:
442
+ tuple: A tuple containing:
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
+
459
+ hom_ay1_str = ""
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':
495
+ is_linear = False
496
+ C = random.randint(1, 5) * random.choice([-1, 1])
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
+
527
+ question = (
528
+ f"A discrete-time system is governed by the equation:\n"
529
+ f"y[n] = {equation_str}\n\n"
530
+ f"Determine if this system is linear by testing the properties of additivity and homogeneity."
531
+ )
532
+
533
+ solution = (
534
+ f"**Given:**\n"
535
+ f"The system equation is y[n] = {equation_str}.\n\n"
536
+
537
+ f"**Step 1:** State the Conditions for Linearity\n"
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"
558
+ f"ya[n] = {hom_ya_str}\n\n"
559
+ f"**Comparison:** {hom_comparison_str}\n\n"
560
+
561
+ f"**Answer:**\n"
562
+ f"{'The system satisfies both additivity and homogeneity, therefore, the system is **linear**.' if is_linear else 'The system fails at least one of the tests (additivity or homogeneity), therefore, the system is **not linear**.'}"
563
+ )
564
+
565
+ return question, solution
566
+
567
+
568
+ # Template 5 (Advanced)
569
+ def template_impulse_response_from_lccde():
570
+ """
571
+ Finding the Impulse Response from a Difference Equation (Advanced)
572
+
573
+ Scenario:
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]}
582
+ 2. Causality: h[n] = 0 for n < 0
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
590
+
591
+ order = random.choice(['first', 'second'])
592
+
593
+ # Generate coefficients with small integer values for clarity
594
+ b0 = random.randint(1, 4) * random.choice([-1, 1])
595
+ a1 = random.randint(1, 5) * random.choice([-1, 1])
596
+
597
+ # Helper lambda to format coefficients into strings like "+ 3" or "- 2"
598
+ fmt = lambda c, v: f"+ {c}" if c > 0 else f"- {abs(c)}"
599
+
600
+ if order == 'first':
601
+ # For first order, we can have b0*x[n] and b1*x[n-1] terms
602
+ b1 = random.randint(1, 4) * random.choice([-1, 1])
603
+
604
+ # Build equation strings
605
+ y_terms = f"y[n] {fmt(a1, 'y[n-1]')}y[n-1]"
606
+ # Randomly omit b1 term for variety
607
+ if random.random() < 0.4:
608
+ b1 = 0
609
+ x_terms = f"{b0}x[n]"
610
+ if b1 != 0:
611
+ x_terms += f" {fmt(b1, 'x[n-1]')}x[n-1]"
612
+
613
+ equation_str = f"{y_terms} = {x_terms}"
614
+
615
+ # 2. Perform the core calculation for a first-order system
616
+ h0 = b0
617
+ h1 = b1 - a1 * h0
618
+
619
+ term1_str = f"{h0}*delta[n]"
620
+
621
+ h_decay_expr = ""
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
+
629
+ # 3. Generate the solution string for a first-order system
630
+ solution_steps = (
631
+ f"**Step 3:** Solve Recursively for Initial Conditions\n"
632
+ f"We assume the system is causal, so **h[n] = 0 for n < 0**.\n\n"
633
+ f"**For n = 0:**\n"
634
+ f"h[0] {fmt(a1, 'h[-1]')}h[-1] = {b0}*delta[0] {'+ 0' if b1==0 else ' ' + fmt(b1, 'delta[-1]')+'*delta[-1]'}\n"
635
+ f"h[0] {fmt(a1, '0')}*(0) = {b0}*(1) + {b1}*(0)\n"
636
+ f"**h[0] = {h0}**\n\n"
637
+
638
+ f"**For n = 1:**\n"
639
+ f"h[1] {fmt(a1, 'h[0]')}h[0] = {b0}*delta[1] {fmt(b1, 'delta[0]')}*delta[0]\n"
640
+ f"h[1] {fmt(a1, h0)}*({h0}) = {b0}*(0) + {b1}*(1)\n"
641
+ f"h[1] = {b1} - ({a1*h0})\n"
642
+ f"**h[1] = {h1}**\n\n"
643
+
644
+ f"**Step 4:** Find the Homogeneous Solution\n"
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'
656
+ # To guarantee real, distinct roots: D = a1^2 - 4*a2 > 0
657
+ a2 = random.randint(-5, 5)
658
+ if a2 == 0: a2 = 1 # Avoid trivial case
659
+ while a1**2 - 4*a2 <= 0:
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
674
+ h0 = b0
675
+ h1 = b1 - a1 * h0
676
+
677
+ # Correctly solve r^2 + a1*r + a2 = 0
678
+ discriminant = a1**2 - 4*a2
679
+ r1 = (-a1 + math.sqrt(discriminant)) / 2
680
+ r2 = (-a1 - math.sqrt(discriminant)) / 2
681
+ r1_str, r2_str = f"{r1:.2f}", f"{r2:.2f}"
682
+
683
+ # Correctly solve for C1 and C2 with general h[0], h[1]
684
+ if abs(r1 - r2) < 1e-9: # Failsafe for very close roots
685
+ r1, r2 = round(r1, 2), round(r2, 2)
686
+ C1 = (h1 - h0 * r2) / (r1 - r2)
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"
700
+ f"We assume the system is causal, so **h[n] = 0 for n < 0**.\n\n"
701
+ f"**For n = 0:**\n"
702
+ f"h[0] {fmt(a1, 'h[-1]')}h[-1] {fmt(a2, 'h[-2]')}h[-2] = {b0}*delta[0] ... (other delta terms are 0)\n"
703
+ f"h[0] {fmt(a1, '0')}*(0) {fmt(a2, '0')}*(0) = {b0}*(1)\n"
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"
720
+ f"1) For n=0: h[0] = C1 + C2 => {h0} = C1 + C2\n"
721
+ f"2) For n=1: h[1] = C1*({r1_str}) + C2*({r2_str}) => {h1} = {r1_str}*C1 + {r2_str}*C2\n\n"
722
+ f"Solving this system yields:\n"
723
+ f"**C1 = {C1_str}** and **C2 = {C2_str}**\n"
724
+ )
725
+
726
+ question = (
727
+ f"A causal LTI system is described by the difference equation:\n"
728
+ f"{equation_str}\n\n"
729
+ f"Find the impulse response h[n] of the system."
730
+ )
731
+
732
+ solution = (
733
+ f"**Given:**\n"
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"
741
+ f"{equation_str.replace('y', 'h').replace('x', 'delta')}\n\n"
742
+
743
+ f"{solution_steps}\n"
744
+
745
+ f"**Answer:**\n"
746
+ f"Substituting the coefficients back into the general form, the impulse response is:\n"
747
+ f"h[n] = {final_h_n}"
748
+ )
749
+
750
+ return question, solution
751
+
752
+
753
+ def main():
754
+ """
755
+ Generate numerous instances of each discrete time signals template
756
+ with different random seeds and write the results to a JSONL file.
757
+ """
758
+ import json
759
+ import os
760
+
761
+ # Define the output path (Modify this path according to where you are running the code from)
762
+ output_file = "testset/electrical_engineering/signals_and_systems/discrete_time_signals.jsonl"
763
+
764
+ # Create the directory if it doesn't exist
765
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
766
+
767
+ # List of template functions with their ID and level
768
+ templates = [
769
+ (template_signal_operations, "signal_operations", "Easy"),
770
+ (template_system_properties_memory_causality, "system_properties_memory_causality", "Easy"),
771
+ (template_finite_convolution, "finite_convolution", "Intermediate"),
772
+ (template_system_property_linearity, "system_property_linearity", "Intermediate"),
773
+ (template_impulse_response_from_lccde, "impulse_response_from_lccde", "Advanced"),
774
+ ]
775
+
776
+ # List to store all generated problems
777
+ all_problems = []
778
+
779
+ # Generate problems for each template
780
+ for template_func, id_name, level in templates:
781
+ for _ in range(50):
782
+ # Generate a unique seed for each problem
783
+ seed = random.randint(1_000_000_000, 4_000_000_000)
784
+ random.seed(seed)
785
+
786
+ # Generate the problem and solution
787
+ question, solution = template_func()
788
+
789
+ # Create a JSON entry
790
+ problem_entry = {
791
+ "seed": seed,
792
+ "branch": "electrical_engineering",
793
+ "domain": "signals_and_systems",
794
+ "area": "discrete_time_signals",
795
+ "id": id_name,
796
+ "level": level,
797
+ "question": question,
798
+ "solution": solution
799
+ }
800
+
801
+ # Add to the list of problems
802
+ all_problems.append(problem_entry)
803
+
804
+ # Shuffle the problems to mix templates and levels
805
+ random.shuffle(all_problems)
806
+
807
+ # Write all problems to a .jsonl file
808
+ with open(output_file, "w") as file:
809
+ for problem in all_problems:
810
+ file.write(json.dumps(problem))
811
+ file.write("\n")
812
+
813
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
814
+
815
+
816
+ if __name__ == "__main__":
817
+ main()
data/templates/branches/mechanical_engineering/constants.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # E is the Modulus of Elasticity.
2
+ MATERIAL_PROPERTIES = {
3
+ # Metals (Common)
4
+ 'Steel': {'E_GPa': 200, 'E_ksi': 29000, 'nu': 0.30},
5
+ 'Stainless Steel': {'E_GPa': 190, 'E_ksi': 27500, 'nu': 0.30}, # 304 Stainless
6
+ 'Aluminum': {'E_GPa': 69, 'E_ksi': 10000, 'nu': 0.33}, # General purpose 1100, 3003 Al
7
+ 'Aluminum 6061-T6': {'E_GPa': 68.9, 'E_ksi': 10000, 'nu': 0.33}, # A common specific alloy
8
+ 'Copper': {'E_GPa': 117, 'E_ksi': 17000, 'nu': 0.34},
9
+ 'Brass': {'E_GPa': 102, 'E_ksi': 14800, 'nu': 0.34}, # Yellow Brass (CuZn37)
10
+ 'Bronze': {'E_GPa': 110, 'E_ksi': 16000, 'nu': 0.34}, # Phosphor Bronze
11
+ 'Titanium': {'E_GPa': 116, 'E_ksi': 16800, 'nu': 0.34}, # Commercially Pure
12
+ 'Titanium Alloy (6Al-4V)': {'E_GPa': 114, 'E_ksi': 16500, 'nu': 0.34},
13
+ 'Magnesium': {'E_GPa': 45, 'E_ksi': 6500, 'nu': 0.29}, # AZ31B alloy
14
+ 'Tungsten': {'E_GPa': 411, 'E_ksi': 59600, 'nu': 0.28},
15
+ 'Cast Iron': {'E_GPa': 170, 'E_ksi': 24600, 'nu': 0.26}, # Gray Cast Iron
16
+ 'Nickel': {'E_GPa': 207, 'E_ksi': 30000, 'nu': 0.31},
17
+ 'Lead': {'E_GPa': 16, 'E_ksi': 2300, 'nu': 0.44}, # Added a common soft metal
18
+
19
+ # Polymers/Plastics
20
+ 'Nylon': {'E_GPa': 2.1, 'E_ksi': 300, 'nu': 0.39}, # Nylon 6/6
21
+ 'Polycarbonate': {'E_GPa': 2.3, 'E_ksi': 334, 'nu': 0.38},
22
+ 'ABS': {'E_GPa': 2.0, 'E_ksi': 290, 'nu': 0.35},
23
+ 'PVC (rigid)': {'E_GPa': 3.0, 'E_ksi': 435, 'nu': 0.38},
24
+ 'PTFE (Teflon)': {'E_GPa': 0.5, 'E_ksi': 72, 'nu': 0.46},
25
+ 'Polyethylene (HDPE)': {'E_GPa': 1.1, 'E_ksi': 160, 'nu': 0.42},
26
+ 'Epoxy': {'E_GPa': 3.0, 'E_ksi': 435, 'nu': 0.38}, # Unreinforced epoxy resin
27
+ 'Natural Rubber': {'E_GPa': 0.0015, 'E_ksi': 0.22, 'nu': 0.4999}, # ~0.5 (nearly incompressible)
28
+
29
+ # Composites & Other
30
+ 'Carbon Fiber Reinforced Polymer (CFRP)': {'E_GPa': 150, 'E_ksi': 21750, 'nu': 0.30}, # Direction-dependent, this is a typical in-plane value
31
+ 'Fiberglass (GFRP)': {'E_GPa': 45, 'E_ksi': 6500, 'nu': 0.25}, # Direction-dependent, typical in-plane value
32
+ 'Concrete': {'E_GPa': 30, 'E_ksi': 4350, 'nu': 0.15}, # Highly variable, common approx.
33
+ 'Glass (Borosilicate)': {'E_GPa': 70, 'E_ksi': 10150, 'nu': 0.20},
34
+ 'Ceramic (Alumina Al2O3)': {'E_GPa': 370, 'E_ksi': 53700, 'nu': 0.22},
35
+ }
36
+
37
+
38
+ # Typical Shear Modulus (G) Values for Engineering Materials (GPa)
39
+ # Note: Values are representative and can vary with alloy composition and heat treatment.
40
+ SHEAR_MODULUS_VALUES = {
41
+ # Metals (Common Alloys)
42
+ "Steel (A36)": 77.2,
43
+ "Stainless Steel (304)": 77.0,
44
+ "Aluminum 6061-T6": 26.0,
45
+ "Aluminum 2024-T4": 28.0,
46
+ "Brass (C36000)": 39.0,
47
+ "Copper (CDA 110)": 44.7,
48
+ "Bronze (Phosphor 510)": 41.4,
49
+ "Titanium Alloy (Ti-6Al-4V)": 41.4,
50
+ "Magnesium Alloy (AZ31B)": 16.5,
51
+ "Tungsten": 160.0,
52
+ "Molybdenum": 118.0,
53
+ "Lead": 5.9,
54
+
55
+ # Other Materials
56
+ "Gray Cast Iron": 44.0, # Note: Anisotropic property, value is an approximation.
57
+ "Nylon 6/6": 1.1, # Polymers have much lower moduli
58
+ "Polycarbonate": 0.9,
59
+ "Concrete": 22.0, # Highly variable, this is a common estimate for calculation
60
+ "Glass": 26.2,
61
+
62
+ # Composites (Highly variable based on fiber orientation and volume fraction)
63
+ # Value given is an approximate in-plane shear modulus.
64
+ "Carbon Fiber Epoxy (Unidirectional, in-plane)": 5.0,
65
+ }
66
+
67
+ # Standard atmospheric pressure in kPa
68
+ ATMOSPHERIC_PRESSURE_KPA = 101.325
69
+
70
+ # Standard acceleration due to gravity in m/s^2
71
+ GRAVITY = 9.81
72
+
73
+
74
+ # Densities of common fluids in kg/m^3 at 20°C and 1 atm, unless specified.
75
+ FLUID_DENSITIES = {
76
+ # Water-based
77
+ "Fresh Water": 998, # More precise value for 20°C
78
+ "Sea Water": 1025,
79
+ "Brine (20% NaCl)": 1150, # Common industrial solution
80
+
81
+ # Oils and Hydrocarbons
82
+ "SAE 10W Oil": 870,
83
+ "SAE 30 Oil": 917,
84
+ "Crude Oil": 870, # Typical average value
85
+ "Kerosene": 810,
86
+ "Diesel Fuel": 850,
87
+ "Gasoline": 726,
88
+ "Engine Oil": 888,
89
+ "Hydraulic Oil": 850, # Typical average value
90
+
91
+ # Alcohols and Solvents
92
+ "Ethyl Alcohol (Ethanol)": 789,
93
+ "Methyl Alcohol (Methanol)": 791,
94
+ "Isopropyl Alcohol (IPA)": 786,
95
+ "Acetone": 791,
96
+ "Turpentine": 870,
97
+ "Benzene": 876,
98
+ "Toluene": 867,
99
+ "Xylene": 870,
100
+
101
+ # Cryogens & Liquefied Gases
102
+ "Liquid Nitrogen (at -196°C)": 807,
103
+ "Liquid Oxygen (at -183°C)": 1141,
104
+ "Liquid Hydrogen (at -253°C)": 71,
105
+ "Liquid Propane": 493, # At 25°C, under pressure
106
+
107
+ # High-Density Liquids
108
+ "Mercury": 13550,
109
+ "Glycerin": 1260,
110
+ "Chloroform": 1489,
111
+ "Carbon Tetrachloride": 1594,
112
+ "Bromine": 3120,
113
+ "Sulfuric Acid (98%)": 1830,
114
+ "Hydrochloric Acid (37%)": 1190, # Also known as Muriatic Acid
115
+ "Nitric Acid (68%)": 1350,
116
+
117
+ # Common Liquids
118
+ "Milk": 1035,
119
+ "Whole Blood": 1060, # Typical value for human blood
120
+ "Ethylene Glycol (Antifreeze)": 1113,
121
+ "Corn Syrup": 1380,
122
+ "Olive Oil": 910,
123
+ "Vegetable Oil": 920,
124
+ "R-134a Refrigerant (Saturated Liquid at 25°C)": 1206,
125
+
126
+ # Gases (at 1 atm, for comparison - though not for hydrostatic problems!)
127
+ "Air (at 20°C)": 1.2,
128
+ "Helium (at 20°C)": 0.166,
129
+ "Carbon Dioxide (at 20°C)": 1.84
130
+ }
131
+
132
+ # Densities of various solid materials in kg/m^3
133
+ MATERIAL_DENSITIES = {
134
+ # Woods & Natural Materials (generally float in water)
135
+ "Pine Wood": 500,
136
+ "Oak Wood": 750,
137
+ "Cork": 240,
138
+ "Teak Wood": 630,
139
+ "Maple Wood": 740,
140
+ "Ebony Wood": 1200, # Sinks in water
141
+ "Bamboo": 300,
142
+ "Rubber (Natural)": 950,
143
+
144
+ # Plastics & Synthetics (range from floating to sinking)
145
+ "Polypropylene (PP)": 900,
146
+ "Polyethylene (LDPE)": 920,
147
+ "Polyethylene (HDPE)": 950,
148
+ "Nylon": 1150,
149
+ "Polystyrene (PS)": 1050,
150
+ "PVC (Rigid)": 1380,
151
+ "PTFE (Teflon)": 2200,
152
+ "Acrylic (Plexiglas)": 1180,
153
+
154
+ # Biological Materials
155
+ "Human Body (avg.)": 985, # Slightly less than water; most people float
156
+ "Apple": 800,
157
+ "Bone": 1850,
158
+
159
+ # Lightweight & Porous Materials
160
+ "Aerogel": 3,
161
+ "Pumice": 700, # The only rock that floats!
162
+ "Brick": 1700,
163
+ "Concrete (avg.)": 2400,
164
+
165
+ # Common Metals & Alloys (will sink in water, float in mercury)
166
+ "Aluminum": 2710,
167
+ "Titanium": 4500,
168
+ "Zinc": 7140,
169
+ "Tin": 7280,
170
+ "Iron (Wrought)": 7750,
171
+ "Steel (Carbon)": 7850,
172
+ "Brass": 8600,
173
+ "Copper": 8940,
174
+ "Nickel": 8900,
175
+ "Silver": 10500,
176
+ "Lead": 11340,
177
+ "Uranium": 19100,
178
+ "Gold": 19300,
179
+ "Tungsten": 19600,
180
+ "Platinum": 21450,
181
+ "Osmium": 22590, # The densest naturally occurring element
182
+
183
+ # Other Materials
184
+ "Ice (0°C)": 917,
185
+ "Wax (Paraffin)": 900,
186
+ "Glass (Window)": 2500,
187
+ "Glass (Crystal)": 2900,
188
+ "Graphite": 2100,
189
+ "Diamond": 3500,
190
+ "Quartz": 2650,
191
+ "Granite": 2700,
192
+ "Marble": 2700,
193
+ "Sandstone": 2300,
194
+ "Limestone": 2500,
195
+ "Cork Board": 240,
196
+ "Paper": 800,
197
+ "Leather (Dry)": 860,
198
+ "Chalk": 2500,
199
+ "Asphalt": 1100,
200
+ "Coal (Anthracite)": 1500,
201
+ }
202
+
203
+ OBJECT_SHAPES = [
204
+ "sphere", "cube", "irregular block", "cylinder", "anchor",
205
+ "rectangular prism", "cone", "pyramid", "torpedo", "submarine hull",
206
+ "ball", "ingot", "pipe section", "bead", "marble",
207
+ "statue", "metal ball", "wooden block", "concrete piling", "boat hull",
208
+ "metal rod", "canister", "storage tank", "buoy", "weight",
209
+ "metal box", "stone", "boulder", "metal cylinder", "plastic container"
210
+ ]
211
+
212
+ OBJECT_MATERIALS = [
213
+ "steel", "aluminum", "copper", "concrete", "plastic",
214
+ "wood (oak)", "wood (pine)", "glass", "gold", "lead",
215
+ "brass", "bronze", "iron", "titanium", "rubber",
216
+ "cork", "foam", "ice", "wax", "ceramic",
217
+ "polyethylene", "PVC", "fiberglass", "granite", "marble",
218
+ "ebony", "balsa wood", "uranium", "magnesium", "tungsten"
219
+ ]
220
+
221
+ # Densities of common fluids in kg/m^3
222
+ # Lighter fluids suitable for the pipe
223
+ PIPE_FLUIDS = {
224
+ # Gases (Very Light)
225
+ "Air": 1.225,
226
+ "Helium": 0.1786,
227
+ "Hydrogen": 0.0899,
228
+ "Natural Gas (Methane)": 0.717,
229
+ "Carbon Dioxide": 1.98,
230
+
231
+ # Light Hydrocarbons & Fuels
232
+ "Gasoline": 726,
233
+ "Kerosene": 810,
234
+ "Diesel Fuel": 850,
235
+ "Jet Fuel (JP-4)": 770,
236
+ "Ethanol": 789,
237
+ "Methanol": 791,
238
+ "Isopropyl Alcohol": 786,
239
+
240
+ # Oils & Lubricants
241
+ "SAE 10 Oil": 870,
242
+ "SAE 20 Oil": 880,
243
+ "SAE 30 Oil": 917,
244
+ "SAE 40 Oil": 945,
245
+ "SAE 50 Oil": 960,
246
+ "Crude Oil (Light)": 825,
247
+ "Crude Oil (Heavy)": 950,
248
+ "Engine Oil": 888,
249
+ "Hydraulic Oil": 860,
250
+
251
+ # Common Liquids
252
+ "Water": 1000,
253
+ "Sea Water": 1025,
254
+ "Milk": 1030,
255
+ "Ethylene Glycol": 1110,
256
+ "Antifreeze (50/50)": 1065,
257
+
258
+ # Cryogenic Fluids
259
+ "Liquid Nitrogen": 810,
260
+ "Liquid Oxygen": 1141,
261
+
262
+ # Chemical Solvents
263
+ "Acetone": 784,
264
+ "Toluene": 867,
265
+ "Benzene": 876,
266
+ "Hexane": 655
267
+ }
268
+
269
+ # Denser fluids suitable for the manometer
270
+ MANOMETER_FLUIDS = {
271
+ # Standard Manometer Fluids
272
+ "Mercury": 13550,
273
+ "Water": 1000, # For gas measurements
274
+ "Sea Water": 1025,
275
+
276
+ # Heavy Oils & Lubricants
277
+ "SAE 50 Oil": 960,
278
+ "SAE 90 Gear Oil": 920,
279
+
280
+ # Organic Liquids
281
+ "Carbon Tetrachloride": 1590,
282
+ "Chloroform": 1480,
283
+ "Bromine": 3120,
284
+ "Tetrabromoethane": 2960,
285
+
286
+ # Inorganic Solutions
287
+ "Calcium Chloride Solution (40%)": 1390,
288
+ "Zinc Chloride Solution (50%)": 1520,
289
+ "Sodium Polysulfide": 1650,
290
+
291
+ # Specialty Fluids
292
+ "Glycerin": 1260,
293
+ "Diiodomethane": 3325,
294
+ "Acetylene Tetrabromide": 2960,
295
+
296
+ # Molten Metals (for high-temperature applications)
297
+ "Gallium": 6095,
298
+ "Tin": 6980,
299
+ "Zinc": 6570,
300
+
301
+ # Very Heavy Fluids
302
+ "Tellurium Mercury": 8100,
303
+ "Tungsten Hexafluoride": 12900
304
+ }
data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_kinematics.py ADDED
@@ -0,0 +1,734 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+
4
+
5
+ # Template 1 (Easy)
6
+ def template_fluid_particle_acceleration():
7
+ """
8
+ Fluid Kinematics: Fluid Particle Acceleration in a 2D Field
9
+
10
+ Scenario:
11
+ This template generates a problem that tests the ability to calculate the
12
+ acceleration of a fluid particle at a specific point and time. It requires
13
+ applying the material derivative to a given 2D unsteady velocity field.
14
+ The problem distinguishes between the local (time-based) and convective
15
+ (space-based) components of acceleration.
16
+
17
+ Core Equations:
18
+ Velocity Field: V(x, y, t) = u(x, y, t)i + v(x, y, t)j
19
+ Acceleration (ax) = du/dt + u*(du/dx) + v*(du/dy)
20
+ Acceleration (ay) = dv/dt + u*(dv/dx) + v*(dv/dy)
21
+ Total Acceleration (a) = sqrt(ax^2 + ay^2)
22
+
23
+ Returns:
24
+ tuple: A tuple containing:
25
+ - str: A question asking for the acceleration components and magnitude.
26
+ - str: A step-by-step solution to the problem.
27
+ """
28
+ # 1. Parameterize the inputs with random values for high diversity
29
+
30
+ # Coefficients for the velocity field polynomials
31
+ # u = A*x*t + B*y**2
32
+ # v = C*x*y + D*t**2
33
+ A = random.randint(1, 10)
34
+ B = random.randint(1, 10)
35
+ C = random.randint(1, 10)
36
+ D = random.randint(1, 10)
37
+
38
+ # Specific point (x, y) and time (t) for evaluation
39
+ x_point = round(random.uniform(0.5, 5.0), 1)
40
+ y_point = round(random.uniform(0.5, 5.0), 1)
41
+ t_point = round(random.uniform(0.1, 3.0), 1)
42
+
43
+ # Standardize precision for all calculations and outputs
44
+ precision = 3
45
+
46
+ # 2. Perform the core calculations for the solution
47
+
48
+ # Step A: Evaluate velocity components u and v at the given point and time
49
+ u_val = A * x_point * t_point + B * y_point**2
50
+ v_val = C * x_point * y_point + D * t_point**2
51
+
52
+ # Step B: Calculate all necessary partial derivatives
53
+ # For u(x, y, t) = A*x*t + B*y^2
54
+ du_dt = A * x_point
55
+ du_dx = A * t_point
56
+ du_dy = 2 * B * y_point
57
+
58
+ # For v(x, y, t) = C*x*y + D*t^2
59
+ dv_dt = 2 * D * t_point
60
+ dv_dx = C * y_point
61
+ dv_dy = C * x_point
62
+
63
+ # Step C: Calculate the acceleration components (ax and ay)
64
+ # ax = (local acceleration in x) + (convective acceleration in x)
65
+ local_ax = du_dt
66
+ convective_ax = u_val * du_dx + v_val * du_dy
67
+ ax = local_ax + convective_ax
68
+
69
+ # ay = (local acceleration in y) + (convective acceleration in y)
70
+ local_ay = dv_dt
71
+ convective_ay = u_val * dv_dx + v_val * dv_dy
72
+ ay = local_ay + convective_ay
73
+
74
+ # Step D: Calculate the magnitude of the total acceleration
75
+ a_magnitude = math.sqrt(ax**2 + ay**2)
76
+
77
+
78
+ # 3. Generate the question and solution strings
79
+
80
+ question = (
81
+ f"The velocity field of a fluid is given by the equations:\n"
82
+ f"u = {A}xt + {B}y^2\n"
83
+ f"v = {C}xy + {D}t^2\n"
84
+ f"where u and v are in m/s, and x and y are in meters, and t is in seconds.\n\n"
85
+ f"Determine the acceleration components (ax and ay) and the magnitude of the "
86
+ f"acceleration for a fluid particle at the point ({x_point}, {y_point}) m at time t = {t_point} s."
87
+ )
88
+
89
+ solution = (
90
+ f"**Given:**\n"
91
+ f"Velocity component u = {A}xt + {B}y^2\n"
92
+ f"Velocity component v = {C}xy + {D}t^2\n"
93
+ f"Point of interest: (x, y) = ({x_point}, {y_point}) m\n"
94
+ f"Time of interest: t = {t_point} s\n\n"
95
+
96
+ f"**Step 1:** Calculate the acceleration component in the x-direction (ax).\n"
97
+ f"The formula is: ax = du/dt + u*(du/dx) + v*(du/dy)\n\n"
98
+
99
+ f"First, find the required partial derivatives of u:\n"
100
+ f"du/dt = d/dt({A}xt + {B}y^2) = {A}x -> At ({x_point}, {t_point}), du/dt = {A} * {x_point} = {round(du_dt, precision)}\n"
101
+ f"du/dx = d/dx({A}xt + {B}y^2) = {A}t -> At ({x_point}, {t_point}), du/dx = {A} * {t_point} = {round(du_dx, precision)}\n"
102
+ f"du/dy = d/dy({A}xt + {B}y^2) = {2*B}y -> At ({y_point}), du/dy = {2*B} * {y_point} = {round(du_dy, precision)}\n\n"
103
+
104
+ f"Next, evaluate u and v at the specified point and time:\n"
105
+ f"u = {A}({x_point})({t_point}) + {B}({y_point})^2 = {round(u_val, precision)} m/s\n"
106
+ f"v = {C}({x_point})({y_point}) + {D}({t_point})^2 = {round(v_val, precision)} m/s\n\n"
107
+
108
+ f"Now, substitute these values into the acceleration formula for ax:\n"
109
+ f"ax = (du/dt) + u*(du/dx) + v*(du/dy)\n"
110
+ f"ax = {round(du_dt, precision)} + ({round(u_val, precision)})*({round(du_dx, precision)}) + ({round(v_val, precision)})*({round(du_dy, precision)})\n"
111
+ f"ax = {round(local_ax, precision)} + {round(convective_ax, precision)} = {round(ax, precision)} m/s^2\n\n"
112
+
113
+ f"**Step 2:** Calculate the acceleration component in the y-direction (ay).\n"
114
+ f"The formula is: ay = dv/dt + u*(dv/dx) + v*(dv/dy)\n\n"
115
+
116
+ f"First, find the required partial derivatives of v:\n"
117
+ f"dv/dt = d/dt({C}xy + {D}t^2) = {2*D}t -> At ({t_point}), dv/dt = {2*D} * {t_point} = {round(dv_dt, precision)}\n"
118
+ f"dv/dx = d/dx({C}xy + {D}t^2) = {C}y -> At ({y_point}), dv/dx = {C} * {y_point} = {round(dv_dx, precision)}\n"
119
+ f"dv/dy = d/dy({C}xy + {D}t^2) = {C}x -> At ({x_point}), dv/dy = {C} * {x_point} = {round(dv_dy, precision)}\n\n"
120
+
121
+ f"Using the already calculated u and v values:\n"
122
+ f"ay = (dv/dt) + u*(dv/dx) + v*(dv/dy)\n"
123
+ f"ay = {round(dv_dt, precision)} + ({round(u_val, precision)})*({round(dv_dx, precision)}) + ({round(v_val, precision)})*({round(dv_dy, precision)})\n"
124
+ f"ay = {round(local_ay, precision)} + {round(convective_ay, precision)} = {round(ay, precision)} m/s^2\n\n"
125
+
126
+ f"**Step 3:** Calculate the magnitude of the total acceleration.\n"
127
+ f"The formula is: a = sqrt(ax^2 + ay^2)\n"
128
+ f"a = sqrt(({round(ax, precision)})^2 + ({round(ay, precision)})^2)\n"
129
+ f"a = {round(a_magnitude, precision)} m/s^2\n\n"
130
+
131
+ f"**Answer:**\n"
132
+ f"The acceleration components are ax = {round(ax, precision)} m/s^2 and ay = {round(ay, precision)} m/s^2.\n"
133
+ f"The magnitude of the acceleration is {round(a_magnitude, precision)} m/s^2."
134
+ )
135
+
136
+ return question, solution
137
+
138
+
139
+ # Template 2 (Intermediate)
140
+ def template_volumetric_flow_rate():
141
+ """
142
+ Fluid Kinematics: Volumetric Flow Rate from a Velocity Profile
143
+
144
+ Scenario:
145
+ This template assesses the ability to calculate the volumetric flow rate (Q)
146
+ by integrating a given velocity profile over a cross-sectional area. It also
147
+ calculates the average velocity. The problem randomly selects between flow
148
+ in a circular pipe (parabolic profile) and flow in a rectangular channel
149
+ (linear profile).
150
+
151
+ Core Equations:
152
+ Volumetric Flow Rate: Q = integral(u dA)
153
+ Average Velocity: V_avg = Q / A
154
+ Pipe Profile: u(r) = U_max * (1 - (r/R)^2), dA = 2*pi*r dr
155
+ Channel Profile: u(y) = U_max * (y/H), dA = W dy
156
+
157
+ Returns:
158
+ tuple: A tuple containing:
159
+ - str: A question asking for the flow rate and average velocity.
160
+ - str: A step-by-step solution to the problem.
161
+ """
162
+ # 1. Parameterize the inputs with random values
163
+ geometry = random.choice(['pipe', 'channel'])
164
+ u_max = round(random.uniform(0.5, 10.0), 2)
165
+ precision = 4
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 "
183
+ f"velocity U_max is {u_max} m/s at the centerline (r=0).\n\n"
184
+ f"Determine the volumetric flow rate (Q) and the average velocity (V_avg) of the fluid."
185
+ )
186
+
187
+ solution = (
188
+ f"**Given:**\n"
189
+ f"Geometry: Circular Pipe\n"
190
+ f"Diameter (D) = {diameter_mm} mm\n"
191
+ f"Maximum Velocity (U_max) = {u_max} m/s\n"
192
+ f"Velocity Profile: u(r) = U_max * (1 - (r/R)^2)\n\n"
193
+
194
+ f"**Step 1:** Determine pipe dimensions in meters.\n"
195
+ f"Radius (R) = D / 2 = {diameter_mm} / 2 = {diameter_mm/2.0} mm = {radius_m} m\n\n"
196
+
197
+ f"**Step 2:** Set up the integral for volumetric flow rate (Q).\n"
198
+ f"The formula is Q = integral(u dA) over the cross-sectional area.\n"
199
+ f"For a circular pipe, the differential area is a thin ring: dA = 2*pi*r dr.\n"
200
+ f"Q = integral from 0 to R of [U_max * (1 - (r/R)^2)] * (2*pi*r dr)\n"
201
+ f"Q = 2*pi*U_max * integral from 0 to R of (r - r^3/R^2) dr\n\n"
202
+
203
+ f"**Step 3:** Evaluate the integral.\n"
204
+ f"Q = 2*pi*U_max * [r^2/2 - r^4/(4*R^2)] from 0 to R\n"
205
+ f"Q = 2*pi*U_max * [(R^2/2 - R^4/(4*R^2)) - 0]\n"
206
+ f"Q = 2*pi*U_max * [R^2/4] = (pi*R^2*U_max) / 2\n\n"
207
+
208
+ f"**Step 4:** Substitute numerical values to find Q.\n"
209
+ f"Q = (pi * ({radius_m})^2 * {u_max}) / 2\n"
210
+ f"Q = {round(flow_rate, precision)} m^3/s\n\n"
211
+
212
+ f"**Step 5:** Calculate the average velocity (V_avg).\n"
213
+ f"First, calculate the cross-sectional area (A) = pi*R^2\n"
214
+ f"A = pi * ({radius_m})^2 = {round(area, precision)} m^2\n"
215
+ f"V_avg = Q / A = {round(flow_rate, precision)} / {round(area, precision)}\n"
216
+ f"Alternatively, for this profile, V_avg is known to be U_max / 2.\n"
217
+ f"V_avg = {u_max} / 2 = {round(avg_velocity, precision)} m/s\n\n"
218
+
219
+ f"**Answer:**\n"
220
+ f"The volumetric flow rate is {round(flow_rate, precision)} m^3/s, and the average velocity is {round(avg_velocity, precision)} m/s."
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 "
240
+ f"maximum velocity U_max is {u_max} m/s at the top surface (y=H).\n\n"
241
+ f"Determine the volumetric flow rate (Q) and the average velocity (V_avg) of the fluid."
242
+ )
243
+
244
+ solution = (
245
+ f"**Given:**\n"
246
+ f"Geometry: Rectangular Channel\n"
247
+ f"Width (W) = {width_cm} cm\n"
248
+ f"Height (H) = {height_cm} cm\n"
249
+ f"Maximum Velocity (U_max) = {u_max} m/s\n"
250
+ f"Velocity Profile: u(y) = U_max * (y/H)\n\n"
251
+
252
+ f"**Step 1:** Determine channel dimensions in meters.\n"
253
+ f"Width (W) = {width_cm} cm = {width_m} m\n"
254
+ f"Height (H) = {height_cm} cm = {height_m} m\n\n"
255
+
256
+ f"**Step 2:** Set up the integral for volumetric flow rate (Q).\n"
257
+ f"The formula is Q = integral(u dA) over the cross-sectional area.\n"
258
+ f"For a rectangular channel, the differential area is a thin horizontal strip: dA = W dy.\n"
259
+ f"Q = integral from 0 to H of [U_max * (y/H)] * (W dy)\n"
260
+ f"Q = (W*U_max/H) * integral from 0 to H of (y) dy\n\n"
261
+
262
+ f"**Step 3:** Evaluate the integral.\n"
263
+ f"Q = (W*U_max/H) * [y^2/2] from 0 to H\n"
264
+ f"Q = (W*U_max/H) * [(H^2/2) - 0]\n"
265
+ f"Q = (W*U_max*H) / 2\n\n"
266
+
267
+ f"**Step 4:** Substitute numerical values to find Q.\n"
268
+ f"Q = ({width_m} * {u_max} * {height_m}) / 2\n"
269
+ f"Q = {round(flow_rate, precision)} m^3/s\n\n"
270
+
271
+ f"**Step 5:** Calculate the average velocity (V_avg).\n"
272
+ f"First, calculate the cross-sectional area (A) = W * H\n"
273
+ f"A = {width_m} * {height_m} = {round(area, precision)} m^2\n"
274
+ f"V_avg = Q / A = {round(flow_rate, precision)} / {round(area, precision)}\n"
275
+ f"Alternatively, for this profile, V_avg is known to be U_max / 2.\n"
276
+ f"V_avg = {u_max} / 2 = {round(avg_velocity, precision)} m/s\n\n"
277
+
278
+ f"**Answer:**\n"
279
+ f"The volumetric flow rate is {round(flow_rate, precision)} m^3/s, and the average velocity is {round(avg_velocity, precision)} m/s."
280
+ )
281
+
282
+ return question, solution
283
+
284
+
285
+ # Template 3 (Intermediate)
286
+ def template_vorticity_check():
287
+ """
288
+ Fluid Kinematics: Vorticity and Rotational Flow Check
289
+
290
+ Scenario:
291
+ This template introduces the concept of fluid element rotation. It requires
292
+ calculating the curl of a given velocity field to find the vorticity vector.
293
+ This enhanced version uses more complex velocity fields where the vorticity
294
+ itself depends on the spatial coordinates, making the evaluation point critical
295
+ to the final answer. The problem randomly generates either a 2D or 3D field.
296
+
297
+ Core Equations:
298
+ Vorticity Vector: zeta = curl(V) = (dw/dy - dv/dz)i + (du/dz - dw/dx)j + (dv/dx - du/dy)k
299
+ Irrotational Flow: A flow is irrotational if the vorticity vector is the zero vector.
300
+
301
+ Returns:
302
+ tuple: A tuple containing:
303
+ - str: A question asking for the vorticity vector and a rotationality check.
304
+ - str: A step-by-step solution to the problem.
305
+ """
306
+ # 1. Parameterize the inputs with random values
307
+ flow_type = random.choice(['2D', '3D'])
308
+ precision = 3
309
+
310
+ # Random coefficients for the velocity field polynomials.
311
+ # Allowing zeros increases variety, sometimes creating simpler or irrotational cases.
312
+ A = random.randint(-4, 4)
313
+ B = random.randint(-4, 4)
314
+ C = random.randint(-4, 4)
315
+
316
+ # Evaluation point
317
+ x_point = round(random.uniform(0.5, 3.0), 1)
318
+ y_point = round(random.uniform(0.5, 3.0), 1)
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"
337
+ f"v = {B}x^2 + {C}y^2\n"
338
+ f"where u and v are in m/s, and x and y are in meters.\n\n"
339
+ f"Calculate the vorticity at the point ({x_point}, {y_point}) m and "
340
+ f"determine if the flow is rotational or irrotational at that point."
341
+ )
342
+
343
+ solution = (
344
+ f"**Given:**\n"
345
+ f"Velocity component u = {A}xy\n"
346
+ f"Velocity component v = {B}x^2 + {C}y^2\n"
347
+ f"Point of interest: (x, y) = ({x_point}, {y_point}) m\n\n"
348
+
349
+ f"**Step 1:** State the formula for vorticity in a 2D flow.\n"
350
+ f"For a 2D flow in the x-y plane, the vorticity vector is (0, 0, zeta_z).\n"
351
+ f"The formula for the z-component is: zeta_z = (dv/dx - du/dy)\n\n"
352
+
353
+ f"**Step 2:** Calculate the necessary partial derivatives.\n"
354
+ f" du/dy = d/dy({A}xy) = {A}x\n"
355
+ f" dv/dx = d/dx({B}x^2 + {C}y^2) = {2*B}x\n\n"
356
+
357
+ f"**Step 3:** Evaluate the partial derivatives at the point ({x_point}, {y_point}).\n"
358
+ f" du/dy at x={x_point} -> {A}({x_point}) = {round(du_dy_val, precision)}\n"
359
+ f" dv/dx at x={x_point} -> {2*B}({x_point}) = {round(dv_dx_val, precision)}\n\n"
360
+
361
+ f"**Step 4:** Substitute the derivative values to find the vorticity component.\n"
362
+ f" zeta_z = ({round(dv_dx_val, precision)}) - ({round(du_dy_val, precision)}) = {round(zeta_z, precision)}\n"
363
+ f"The vorticity vector at this point is (0, 0, {round(zeta_z, precision)}) rad/s.\n\n"
364
+
365
+ f"**Step 5:** Determine if the flow is rotational at this point.\n"
366
+ f"A flow is irrotational at a point if its vorticity is zero there.\n"
367
+ f"Since the vorticity component zeta_z = {round(zeta_z, precision)}, which is {'zero' if is_irrotational else 'not zero'},\n"
368
+ f"the flow is {'irrotational' if is_irrotational else 'rotational'} at this specific point.\n\n"
369
+
370
+ f"**Answer:**\n"
371
+ f"The vorticity at ({x_point}, {y_point}) is (0, 0, {round(zeta_z, precision)}) rad/s. The flow is {'irrotational' if is_irrotational else 'rotational'} at this point."
372
+ )
373
+
374
+ else: # flow_type == '3D'
375
+ D = random.randint(-4, 4)
376
+ E = random.randint(-4, 4)
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
+
388
+ dv_dx_val = C
389
+ dv_dz_val = 2 * B * z_point
390
+
391
+ dw_dx_val = 2 * D * x_point
392
+ dw_dy_val = E
393
+
394
+ zeta_x = dw_dy_val - dv_dz_val
395
+ zeta_y = du_dz_val - dw_dx_val
396
+ zeta_z = dv_dx_val - du_dy_val
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"
404
+ f"v = {B}z^2 + {C}x\n"
405
+ f"w = {D}x^2 + {E}y\n"
406
+ f"where u, v, w are in m/s, and x, y, z are in meters.\n\n"
407
+ f"Calculate the vorticity vector at the point ({x_point}, {y_point}, {z_point}) m and "
408
+ f"determine if the flow is rotational or irrotational at that point."
409
+ )
410
+
411
+ solution = (
412
+ f"**Given:**\n"
413
+ f"Velocity field: u={A}y^2, v={B}z^2 + {C}x, w={D}x^2 + {E}y\n"
414
+ f"Point of interest: ({x_point}, {y_point}, {z_point}) m\n\n"
415
+
416
+ f"**Step 1:** State the formula for the 3D vorticity vector.\n"
417
+ f"zeta = (dw/dy - dv/dz)i + (du/dz - dw/dx)j + (dv/dx - du/dy)k\n\n"
418
+
419
+ f"**Step 2:** Calculate all necessary partial derivatives.\n"
420
+ f"From u = {A}y^2: du/dy = {2*A}y, du/dz = 0\n"
421
+ f"From v = {B}z^2 + {C}x: dv/dx = {C}, dv/dz = {2*B}z\n"
422
+ f"From w = {D}x^2 + {E}y: dw/dx = {2*D}x, dw/dy = {E}\n\n"
423
+
424
+ f"**Step 3:** Evaluate the derivatives at the point ({x_point}, {y_point}, {z_point}).\n"
425
+ f" du/dy = {2*A}({y_point}) = {round(du_dy_val, precision)}\n"
426
+ f" dv/dx = {C}\n"
427
+ f" dv/dz = {2*B}({z_point}) = {round(dv_dz_val, precision)}\n"
428
+ f" dw/dx = {2*D}({x_point}) = {round(dw_dx_val, precision)}\n"
429
+ f" dw/dy = {E}\n"
430
+ f" (du/dz is always 0)\n\n"
431
+
432
+ f"**Step 4:** Calculate each component of the vorticity vector.\n"
433
+ f"zeta_x = dw/dy - dv/dz = {E} - ({round(dv_dz_val, precision)}) = {round(zeta_x, precision)}\n"
434
+ f"zeta_y = du/dz - dw/dx = 0 - ({round(dw_dx_val, precision)}) = {round(zeta_y, precision)}\n"
435
+ f"zeta_z = dv/dx - du/dy = {C} - ({round(du_dy_val, precision)}) = {round(zeta_z, precision)}\n\n"
436
+
437
+ f"**Step 5:** Assemble the vorticity vector and determine if the flow is rotational.\n"
438
+ f"The vorticity vector at this point is ({round(zeta_x, precision)}, {round(zeta_y, precision)}, {round(zeta_z, precision)}) rad/s.\n"
439
+ f"A flow is irrotational at a point if its vorticity is the zero vector (0, 0, 0).\n"
440
+ f"Since the vector is {'the zero vector' if is_irrotational else 'not the zero vector'},\n"
441
+ f"the flow is {'irrotational' if is_irrotational else 'rotational'} at this specific point.\n\n"
442
+
443
+ f"**Answer:**\n"
444
+ f"The vorticity vector at ({x_point}, {y_point}, {z_point}) is ({round(zeta_x, precision)}, {round(zeta_y, precision)}, {round(zeta_z, precision)}) rad/s. "
445
+ f"The flow is {'irrotational' if is_irrotational else 'rotational'} at this point."
446
+ )
447
+
448
+ return question, solution
449
+
450
+
451
+ # Template 4 (Advanced)
452
+ def template_particle_pathline():
453
+ """
454
+ Fluid Kinematics: Finding a Particle's Pathline in a Steady Flow
455
+
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)
465
+ Velocity Field Used: u = A*x, v = -B*y
466
+
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)
487
+ yf = y0 * math.exp(-B * tf)
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
+
496
+ solution = (
497
+ f"**Given:**\n"
498
+ f"Velocity field: u = {A}x, v = -{B}y\n"
499
+ f"Initial position (x0, y0) = ({x0}, {y0})\n"
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"
508
+ f"Integrating both sides gives:\n"
509
+ f" ln(x) = {A}t + C1\n"
510
+ f"Solving for x by exponentiating:\n"
511
+ f" x(t) = exp({A}t + C1) = C * exp({A}t)\n\n"
512
+
513
+ f"**Step 3:** Apply the initial condition x(0) = {x0} to find the constant C.\n"
514
+ f" {x0} = C * exp({A}*0) = C * 1\n"
515
+ f"So, C = {x0}. The specific solution for the x-coordinate is:\n"
516
+ f" x(t) = {x0} * exp({A}t)\n\n"
517
+
518
+ f"**Step 4:** Set up and solve the differential equation for the y-coordinate.\n"
519
+ f"The pathline is defined by dy/dt = v.\n"
520
+ f" dy/dt = -{B}y\n"
521
+ f"Separating variables: (1/y) dy = -{B} dt\n"
522
+ f"Integrating both sides gives:\n"
523
+ f" ln(y) = -{B}t + D1\n"
524
+ f" y(t) = D * exp(-{B}t)\n\n"
525
+
526
+ f"**Step 5:** Apply the initial condition y(0) = {y0} to find the constant D.\n"
527
+ f" {y0} = D * exp(-{B}*0) = D * 1\n"
528
+ f"So, D = {y0}. The specific solution for the y-coordinate is:\n"
529
+ f" y(t) = {y0} * exp(-{B}t)\n\n"
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
541
+
542
+
543
+ # Template 5 (Advanced)
544
+ def template_incompressible_continuity():
545
+ """
546
+ Fluid Kinematics: Deriving a Velocity Component for Incompressible Flow
547
+
548
+ Scenario:
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
557
+
558
+ Returns:
559
+ tuple: A tuple containing:
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 "
587
+ f"given by u = {u_expr}.\n\n"
588
+ f"Determine the simplest possible expression for the y-component of velocity, v(x, y)."
589
+ )
590
+
591
+ solution = (
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 "
634
+ f"given by v = {v_expr}.\n\n"
635
+ f"Determine the simplest possible expression for the x-component of velocity, u(x, y)."
636
+ )
637
+
638
+ solution = (
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
+ )
666
+
667
+ return question, solution
668
+
669
+
670
+ def main():
671
+ """
672
+ Generate numerous instances of each fluid kinematics template
673
+ with different random seeds and write the results to a JSONL file.
674
+ """
675
+ import json
676
+ import os
677
+
678
+ # Define the output path (Modify this path according to where you are running the code from)
679
+ output_file = "testset/mechanical_engineering/fluid_mechanics/fluid_kinematics.jsonl"
680
+
681
+ # Create the directory if it doesn't exist
682
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
683
+
684
+ # List of template functions with their ID and level
685
+ templates = [
686
+ (template_fluid_particle_acceleration, "fluid_particle_acceleration", "Easy"),
687
+ (template_volumetric_flow_rate, "volumetric_flow_rate", "Intermediate"),
688
+ (template_vorticity_check, "vorticity_check", "Intermediate"),
689
+ (template_particle_pathline, "particle_pathline", "Advanced"),
690
+ (template_incompressible_continuity, "incompressible_continuity", "Advanced"),
691
+ ]
692
+
693
+ # List to store all generated problems
694
+ all_problems = []
695
+
696
+ # Generate problems for each template
697
+ for template_func, id_name, level in templates:
698
+ for _ in range(50):
699
+ # Generate a unique seed for each problem
700
+ seed = random.randint(1_000_000_000, 4_000_000_000)
701
+ random.seed(seed)
702
+
703
+ # Generate the problem and solution
704
+ question, solution = template_func()
705
+
706
+ # Create a JSON entry
707
+ problem_entry = {
708
+ "seed": seed,
709
+ "branch": "mechanical_engineering",
710
+ "domain": "fluid_mechanics",
711
+ "area": "fluid_kinematics",
712
+ "id": id_name,
713
+ "level": level,
714
+ "question": question,
715
+ "solution": solution
716
+ }
717
+
718
+ # Add to the list of problems
719
+ all_problems.append(problem_entry)
720
+
721
+ # Shuffle the problems to mix templates and levels
722
+ random.shuffle(all_problems)
723
+
724
+ # Write all problems to a .jsonl file
725
+ with open(output_file, "w") as file:
726
+ for problem in all_problems:
727
+ file.write(json.dumps(problem))
728
+ file.write("\n")
729
+
730
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
731
+
732
+
733
+ if __name__ == "__main__":
734
+ main()
data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_statics.py ADDED
@@ -0,0 +1,616 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ from data.templates.branches.mechanical_engineering.constants import GRAVITY, ATMOSPHERIC_PRESSURE_KPA, FLUID_DENSITIES, MATERIAL_DENSITIES, OBJECT_SHAPES, OBJECT_MATERIALS, PIPE_FLUIDS, MANOMETER_FLUIDS
4
+
5
+
6
+ # Template 1 (Easy)
7
+ def template_hydrostatic_pressure_at_depth():
8
+ """
9
+ Fluid Statics: Hydrostatic Pressure at Depth
10
+
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:
22
+ - str: A question asking for the absolute pressure at a certain depth.
23
+ - str: A step-by-step solution to the problem.
24
+ """
25
+ # 1. Parameterize the inputs with random values
26
+
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
63
+
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
103
+
104
+
105
+ # Template 2 (Easy)
106
+ def template_basic_buoyant_force():
107
+ """
108
+ Fluid Statics: Basic Buoyant Force on a Submerged Object
109
+
110
+ Scenario:
111
+ This template applies Archimedes' principle to calculate the buoyant
112
+ force on a fully submerged object with a known volume. It reinforces
113
+ the direct relationship between buoyant force and the weight of the
114
+ displaced fluid.
115
+
116
+ Core Equation:
117
+ F_buoyant = rho_fluid * g * V_displaced
118
+
119
+ Returns:
120
+ tuple: A tuple containing:
121
+ - str: A question asking for the buoyant force on an object.
122
+ - str: A step-by-step solution to the problem.
123
+ """
124
+ # 1. Parameterize the inputs with random values
125
+
126
+ # Randomly select a fluid and its properties
127
+ fluid_name, density_rho = random.choice(list(FLUID_DENSITIES.items()))
128
+
129
+ # Randomly select descriptive properties for the object
130
+ shape = random.choice(OBJECT_SHAPES)
131
+ material = random.choice(OBJECT_MATERIALS)
132
+
133
+ # Randomize the object's volume in cubic meters
134
+ object_volume = round(random.uniform(0.05, 2.5), 3)
135
+
136
+ # Standardize precision for final outputs
137
+ precision = 3
138
+
139
+ # 2. Perform the core calculations for the solution
140
+
141
+ # Step A: Calculate the buoyant force in Newtons (N)
142
+ # Since the object is fully submerged, the displaced volume equals the object's volume.
143
+ buoyant_force_n = density_rho * GRAVITY * object_volume
144
+
145
+ # Step B: Convert to kilonewtons (kN) if the value is large, for better readability
146
+ buoyant_force_kn = buoyant_force_n / 1000
147
+
148
+ # 3. Generate the question and solution strings
149
+
150
+ question = (
151
+ f"A solid {material} {shape} with a total volume of {object_volume} m^3 is "
152
+ f"fully submerged in a tank of {fluid_name.lower()}. "
153
+ f"Given that the density of {fluid_name.lower()} is {density_rho} kg/m^3, "
154
+ f"calculate the buoyant force acting on the {shape}."
155
+ )
156
+
157
+ solution = (
158
+ f"**Given:**\n"
159
+ f"Object Volume (V): {object_volume} m^3\n"
160
+ f"Fluid: {fluid_name}\n"
161
+ f"Density of Fluid (rho): {density_rho} kg/m^3\n"
162
+ f"Acceleration due to Gravity (g): {GRAVITY} m/s^2\n\n"
163
+
164
+ f"**Step 1:** Identify the Principle\n"
165
+ f"According to Archimedes' principle, the buoyant force (F_buoyant) on a submerged "
166
+ f"object is equal to the weight of the fluid it displaces.\n"
167
+ f"Since the object is fully submerged, the volume of displaced fluid is equal to the "
168
+ f"volume of the object itself.\n\n"
169
+
170
+ f"**Step 2:** Apply the Buoyant Force Formula\n"
171
+ f"The formula for buoyant force is: F_buoyant = rho_fluid * g * V_displaced\n"
172
+ f"F_buoyant = {density_rho} kg/m^3 * {GRAVITY} m/s^2 * {object_volume} m^3\n"
173
+ f"F_buoyant = {round(buoyant_force_n, precision)} N\n\n"
174
+
175
+ f"**Step 3:** Convert the Result to Kilonewtons (kN) (Optional)\n"
176
+ f"For larger force values, it is common to express the result in kilonewtons.\n"
177
+ f"F_buoyant = {round(buoyant_force_n, precision)} N / 1000 = {round(buoyant_force_kn, precision)} kN\n\n"
178
+
179
+ f"**Answer:**\n"
180
+ f"The buoyant force acting on the {shape} is {round(buoyant_force_n, precision)} N, "
181
+ f"which is equivalent to {round(buoyant_force_kn, precision)} kN."
182
+ )
183
+
184
+ return question, solution
185
+
186
+
187
+ # Template 3 (Intermediate)
188
+ def template_utube_manometer():
189
+ """
190
+ Fluid Statics: U-Tube Manometer Pressure Measurement
191
+
192
+ Scenario:
193
+ This template generates a classic problem involving a U-tube manometer
194
+ used to measure the gauge pressure of a fluid inside a pipe. The solution
195
+ requires applying the principle of hydrostatic equilibrium by balancing
196
+ the pressure contributions from different fluid columns.
197
+
198
+ Core Equation:
199
+ P_gauge = (rho_2 * g * h2) - (rho_1 * g * h1)
200
+
201
+ Returns:
202
+ tuple: A tuple containing:
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
+
243
+ # 3. Generate the question and solution strings
244
+
245
+ question = (
246
+ f"A U-tube manometer using {manometer_fluid_name.lower()} (density = {rho2} kg/m^3) is "
247
+ f"connected to a pipe carrying {pipe_fluid_name.lower()} (density = {rho1} kg/m^3). "
248
+ f"The interface between the two fluids is {h1} m below the centerline of the pipe. "
249
+ f"The level of {manometer_fluid_name.lower()} in the arm open to the atmosphere is {h2} m "
250
+ f"higher than the interface. What is the gauge pressure in the pipe?"
251
+ )
252
+
253
+ solution = (
254
+ f"**Given:**\n"
255
+ f"Pipe Fluid: {pipe_fluid_name}, Density (rho1) = {rho1} kg/m^3\n"
256
+ f"Manometer Fluid: {manometer_fluid_name}, Density (rho2) = {rho2} kg/m^3\n"
257
+ f"Height from pipe centerline to interface (h1): {h1} m\n"
258
+ f"Height difference in manometer fluid (h2): {h2} m\n"
259
+ f"Acceleration due to Gravity (g): {GRAVITY} m/s^2\n\n"
260
+
261
+ f"**Step 1:** State the Principle of Manometry\n"
262
+ f"We can determine the pressure in the pipe by starting at the pipe's centerline, moving "
263
+ f"through the fluid columns to the open end, and balancing the pressures. The pressure at the "
264
+ f"same level within a continuous fluid at rest is equal.\n\n"
265
+
266
+ f"**Step 2:** Formulate the Pressure Balance Equation\n"
267
+ f"Let's establish a pressure equation starting from the pipe (P_pipe) and ending at the atmosphere (P_atm). "
268
+ f"The reference level is the interface between the pipe fluid and the manometer fluid.\n"
269
+ f"Pressure from pipe side at interface: P_pipe + (rho1 * g * h1)\n"
270
+ f"Pressure from atmosphere side at interface: P_atm + (rho2 * g * h2)\n"
271
+ f"Equating these gives: P_pipe + (rho1 * g * h1) = P_atm + (rho2 * g * h2)\n\n"
272
+
273
+ f"**Step 3:** Solve for the Gauge Pressure\n"
274
+ f"Gauge pressure is P_gauge = P_pipe - P_atm. Rearranging the equation:\n"
275
+ f"P_gauge = (rho2 * g * h2) - (rho1 * g * h1)\n\n"
276
+
277
+ f"**Step 4:** Calculate the Individual Pressure Terms\n"
278
+ f"Pressure from manometer fluid column = {rho2} * {GRAVITY} * {h2} = {pressure_term2_pa:.2f} Pa\n"
279
+ f"Pressure from pipe fluid column = {rho1} * {GRAVITY} * {h1} = {pressure_term1_pa:.2f} Pa\n\n"
280
+
281
+ f"**Step 5:** Calculate the Final Gauge Pressure\n"
282
+ f"P_gauge = {pressure_term2_pa:.2f} Pa - {pressure_term1_pa:.2f} Pa = {gauge_pressure_pa:.2f} Pa\n\n"
283
+
284
+ f"**Step 6:** Convert the Answer to Kilopascals (kPa)\n"
285
+ f"P_gauge = {gauge_pressure_pa:.2f} Pa / 1000 = {round(gauge_pressure_kpa, precision)} kPa\n\n"
286
+
287
+ f"**Answer:**\n"
288
+ f"The gauge pressure in the pipe is {round(gauge_pressure_kpa, precision)} kPa."
289
+ )
290
+
291
+ return question, solution
292
+
293
+
294
+ # Template 4 (Intermediate)
295
+ def template_floating_object_submersion_depth():
296
+ """
297
+ Fluid Statics: Floating Object Submersion Depth
298
+
299
+ Scenario:
300
+ This template applies the principle of buoyancy to determine how deep
301
+ an object with a uniform cross-section (like a rectangular block or
302
+ cylinder) will float in a liquid. The solution requires equating the
303
+ object's total weight to the buoyant force acting on its submerged part.
304
+
305
+ Core Equations:
306
+ - Weight (W) = rho_object * g * V_total
307
+ - Buoyant Force (F_B) = rho_fluid * g * V_submerged
308
+ - At equilibrium (floating): W = F_B
309
+ - This simplifies to: rho_object * V_total = rho_fluid * V_submerged
310
+
311
+ Returns:
312
+ tuple: A tuple containing:
313
+ - str: A question asking for the submersion depth of a floating object.
314
+ - str: A step-by-step solution to the problem.
315
+ """
316
+ # 1. Parameterize the inputs to ensure a valid floating scenario
317
+
318
+ # Randomly select an object material and a fluid
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
326
+ while rho_object >= rho_fluid and attempt < max_attempts:
327
+ obj_material, rho_object = random.choice(list(MATERIAL_DENSITIES.items()))
328
+ fluid_name, rho_fluid = random.choice(list(FLUID_DENSITIES.items()))
329
+ attempt += 1
330
+
331
+ # Fallback in the rare case a valid pair isn't found quickly
332
+ if rho_object >= rho_fluid:
333
+ obj_material, rho_object = "Pine Wood", 500
334
+ fluid_name, rho_fluid = "Fresh Water", 998
335
+
336
+ # Randomly choose the object's shape (uniform cross-section)
337
+ shape = random.choice(["rectangular block", "cylinder"])
338
+
339
+ # Randomize dimensions based on shape
340
+ if shape == "rectangular block":
341
+ length = round(random.uniform(0.5, 3.0), 2)
342
+ width = round(random.uniform(0.2, 2.0), 2)
343
+ height = round(random.uniform(0.1, 1.5), 2) # This is the total vertical height
344
+ shape_dims_str = f"dimensions {length} m (length) x {width} m (width) x {height} m (height)"
345
+ shape_dims_given_str = (f" - Length (L): {length} m\n"
346
+ f" - Width (W): {width} m\n"
347
+ f" - Total Height (H): {height} m")
348
+ else: # shape == "cylinder"
349
+ radius = round(random.uniform(0.1, 1.5), 2)
350
+ height = round(random.uniform(0.2, 2.5), 2) # This is the total vertical height
351
+ shape_dims_str = f"a radius of {radius} m and a total height of {height} m"
352
+ shape_dims_given_str = (f" - Radius (r): {radius} m\n"
353
+ f" - Total Height (H): {height} m")
354
+
355
+ # Standardize precision for final outputs
356
+ precision = 4
357
+
358
+ # 2. Perform the core calculations for the solution
359
+
360
+ # The core insight is that for a uniform cross-section, the area cancels out.
361
+ # W = F_B => rho_obj * g * (Area * H) = rho_fluid * g * (Area * h_sub)
362
+ # Simplifying gives: rho_obj * H = rho_fluid * h_sub
363
+ submersion_depth = (rho_object / rho_fluid) * height
364
+
365
+ # 3. Generate the question and solution strings
366
+
367
+ question = (
368
+ f"A {shape} made of {obj_material.lower()} (density = {rho_object} kg/m^3) has "
369
+ f"{shape_dims_str}. The object is placed in a large tank of {fluid_name.lower()} "
370
+ f"(density = {rho_fluid} kg/m^3). Assuming the object floats in a stable, upright "
371
+ f"position, calculate its submersion depth (the vertical height of the object "
372
+ f"that is below the fluid surface)."
373
+ )
374
+
375
+ solution = (
376
+ f"**Given:**\n"
377
+ f"Object Shape: {shape.capitalize()}\n"
378
+ f"{shape_dims_given_str}\n"
379
+ f"Object Density (rho_obj): {rho_object} kg/m^3\n"
380
+ f"Fluid Density (rho_fluid): {rho_fluid} kg/m^3\n\n"
381
+
382
+ f"**Step 1:** State the Principle of Flotation\n"
383
+ f"For an object to float, its total weight (W) must be equal to the buoyant force (F_B) "
384
+ f"exerted by the fluid. The buoyant force is the weight of the displaced fluid.\n"
385
+ f"Equilibrium Condition: W = F_B\n\n"
386
+
387
+ f"**Step 2:** Express Weight and Buoyant Force using Densities\n"
388
+ f"Weight (W) = rho_obj * g * V_total\n"
389
+ f"Buoyant Force (F_B) = rho_fluid * g * V_submerged\n\n"
390
+ f"Setting them equal: \n"
391
+ f"rho_obj * g * V_total = rho_fluid * g * V_submerged\n\n"
392
+
393
+ f"**Step 3:** Simplify for a Uniform Cross-Section\n"
394
+ f"The acceleration of gravity (g) cancels from both sides. For an object with a uniform "
395
+ f"cross-sectional area (A), the volumes can be expressed as V = A * height.\n"
396
+ f"V_total = A * H_total\n"
397
+ f"V_submerged = A * h_submerged\n\n"
398
+ f"Substituting these into the equation:\n"
399
+ f"rho_obj * (A * H_total) = rho_fluid * (A * h_submerged)\n\n"
400
+ f"The cross-sectional area (A) also cancels out, leaving a simple ratio:\n"
401
+ f"rho_obj * H_total = rho_fluid * h_submerged\n\n"
402
+
403
+ f"**Step 4:** Solve for the Submersion Depth (h_submerged)\n"
404
+ f"Rearranging the formula to solve for the unknown depth:\n"
405
+ f"h_submerged = (rho_obj / rho_fluid) * H_total\n\n"
406
+ f"Plugging in the given values:\n"
407
+ f"h_submerged = ({rho_object} / {rho_fluid}) * {height}\n"
408
+ f"h_submerged = {round(submersion_depth, precision)} m\n\n"
409
+
410
+ f"**Answer:**\n"
411
+ f"The submersion depth of the {shape} is {round(submersion_depth, precision)} m."
412
+ )
413
+
414
+ return question, solution
415
+
416
+
417
+ # Template 5 (Advanced)
418
+ def template_hydrostatic_force_on_plane():
419
+ """
420
+ Fluid Statics: Hydrostatic Force and Center of Pressure on a Submerged Plane
421
+
422
+ Scenario:
423
+ This template generates a comprehensive problem requiring the calculation of
424
+ the magnitude of the resultant hydrostatic force on a submerged plane
425
+ surface (e.g., a gate, window) and the location where this force acts,
426
+ known as the center of pressure.
427
+
428
+ Core Equations:
429
+ - Resultant Force (F_R) = rho * g * h_c * A
430
+ - Center of Pressure (y_R) = y_c + (I_xc / (y_c * A))
431
+ where:
432
+ - rho = fluid density
433
+ - g = acceleration of gravity
434
+ - h_c = vertical depth from free surface to the centroid of the area
435
+ - A = area of the submerged surface
436
+ - y_c = inclined distance from free surface to the centroid
437
+ - I_xc = area moment of inertia about the centroidal axis parallel to the surface
438
+
439
+ Returns:
440
+ tuple: A tuple containing:
441
+ - str: A question about hydrostatic force and center of pressure.
442
+ - str: A detailed, step-by-step solution.
443
+ """
444
+ # 1. Parameterize the inputs with random values
445
+
446
+ # Select fluid and shape
447
+ fluid_name, rho = random.choice(list(FLUID_DENSITIES.items()))
448
+ shape = random.choice(["rectangle", "circle"])
449
+
450
+ # Set geometric and positional parameters
451
+ angle_deg = random.choice([30, 45, 60, 90]) # Angle with the horizontal
452
+ angle_rad = math.radians(angle_deg)
453
+ h_top = round(random.uniform(0.5, 5.0), 2) # Vertical depth to top edge of the plane
454
+
455
+ # Randomize dimensions based on shape and calculate geometric properties
456
+ if shape == "rectangle":
457
+ b = round(random.uniform(1.0, 4.0), 2) # width
458
+ h = round(random.uniform(1.0, 4.0), 2) # height
459
+ area = b * h
460
+ I_xc = (b * h**3) / 12
461
+ dist_to_centroid_along_plane = h / 2
462
+ shape_desc = f"a rectangular gate with a width of {b} m and a height of {h} m"
463
+ shape_given = f" - Shape: Rectangle (width b = {b} m, height h = {h} m)"
464
+ else: # shape == "circle"
465
+ r = round(random.uniform(0.5, 2.0), 2) # radius
466
+ area = math.pi * r**2
467
+ I_xc = (math.pi * r**4) / 4
468
+ dist_to_centroid_along_plane = r
469
+ shape_desc = f"a circular viewport with a radius of {r} m"
470
+ shape_given = f" - Shape: Circle (radius r = {r} m)"
471
+
472
+ precision = 4
473
+
474
+ # 2. Perform the core calculations for the solution
475
+
476
+ # Step A: Determine the position of the centroid (yc and hc)
477
+ # yc is the inclined distance from the free surface to the centroid
478
+ # hc is the vertical depth from the free surface to the centroid
479
+ if angle_deg == 90:
480
+ y_top = h_top
481
+ angle_desc = "is positioned vertically"
482
+ else:
483
+ y_top = h_top / math.sin(angle_rad)
484
+ angle_desc = f"is inclined at an angle of {angle_deg} degrees to the horizontal"
485
+
486
+ y_c = y_top + dist_to_centroid_along_plane
487
+ h_c = y_c * math.sin(angle_rad)
488
+
489
+ # Step B: Calculate the resultant force (F_R)
490
+ force_resultant_N = rho * GRAVITY * h_c * area
491
+ force_resultant_kN = force_resultant_N / 1000
492
+
493
+ # Step C: Calculate the location of the center of pressure (y_R)
494
+ # y_R is the inclined distance from the free surface to the center of pressure
495
+ y_R = y_c + (I_xc / (y_c * area))
496
+
497
+ # 3. Generate the question and solution strings
498
+
499
+ question = (
500
+ f"A submerged {shape_desc} {angle_desc} in a tank of {fluid_name.lower()} "
501
+ f"(density = {rho} kg/m^3). The top edge of the gate is {h_top} m vertically below "
502
+ f"the free surface. Calculate:\n"
503
+ f" a) The magnitude of the resultant hydrostatic force on the gate.\n"
504
+ f" b) The location of the center of pressure, measured along the incline of the gate "
505
+ f"from the free surface."
506
+ )
507
+
508
+ solution = (
509
+ f"**Given:**\n"
510
+ f"{shape_given}\n"
511
+ f"Fluid: {fluid_name} (rho = {rho} kg/m^3)\n"
512
+ f"Vertical depth to top edge (h_top): {h_top} m\n"
513
+ f"Angle of inclination (theta): {angle_deg} degrees\n\n"
514
+
515
+ f"**Step 1:** Calculate Geometric Properties of the Gate\n"
516
+ f"Area (A): {area:.{precision}f} m^2\n"
517
+ f"Moment of Inertia about centroid (I_xc): {I_xc:.{precision}f} m^4\n\n"
518
+
519
+ f"**Step 2:** Determine the Location of the Centroid (yc and hc)\n"
520
+ f"The centroid is the geometric center of the gate. We need its position relative to the free surface.\n"
521
+ f" - 'y' distances are measured along the plane's incline.\n"
522
+ f" - 'h' distances are measured vertically.\n\n"
523
+ f"Inclined distance from surface to top edge (y_top) = h_top / sin(theta)\n"
524
+ f" y_top = {h_top} / sin({angle_deg}°) = {y_top:.{precision}f} m\n"
525
+ f"Distance from top edge to centroid along plane = {dist_to_centroid_along_plane:.{precision}f} m\n"
526
+ f"Inclined distance from surface to centroid (y_c) = y_top + dist_to_centroid\n"
527
+ f" y_c = {y_top:.{precision}f} + {dist_to_centroid_along_plane:.{precision}f} = {y_c:.{precision}f} m\n\n"
528
+ f"Vertical depth to centroid (h_c) = y_c * sin(theta)\n"
529
+ f" h_c = {y_c:.{precision}f} * sin({angle_deg}°) = {h_c:.{precision}f} m\n\n"
530
+
531
+ f"**Step 3:** Calculate the Resultant Hydrostatic Force (F_R)\n"
532
+ f"The force is the pressure at the centroid multiplied by the total area.\n"
533
+ f"F_R = rho * g * h_c * A\n"
534
+ f"F_R = {rho} * {GRAVITY} * {h_c:.{precision}f} * {area:.{precision}f}\n"
535
+ f"F_R = {force_resultant_N:.2f} N\n"
536
+ f"F_R = {force_resultant_kN:.{precision}f} kN\n\n"
537
+
538
+ f"**Step 4:** Calculate the Center of Pressure (y_R)\n"
539
+ f"The center of pressure is the point where the resultant force acts. It is always below the centroid.\n"
540
+ f"y_R = y_c + (I_xc / (y_c * A))\n"
541
+ f"y_R = {y_c:.{precision}f} + ({I_xc:.{precision}f} / ({y_c:.{precision}f} * {area:.{precision}f}))\n"
542
+ f"y_R = {y_R:.{precision}f} m\n\n"
543
+
544
+ f"**Answer:**\n"
545
+ f"a) The magnitude of the resultant hydrostatic force is **{force_resultant_kN:.{precision}f} kN**.\n"
546
+ f"b) The center of pressure is located **{y_R:.{precision}f} m** from the free surface, measured down along the angle of the gate."
547
+ )
548
+
549
+ return question, solution
550
+
551
+
552
+ def main():
553
+ """
554
+ Generate numerous instances of each fluid statics template
555
+ with different random seeds and write the results to a JSONL file.
556
+ """
557
+ import json
558
+ import os
559
+
560
+ # Define the output path (Modify this path according to where you are running the code from)
561
+ output_file = "testset/mechanical_engineering/fluid_mechanics/fluid_statics.jsonl"
562
+
563
+ # Create the directory if it doesn't exist
564
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
565
+
566
+ # List of template functions with their ID and level
567
+ templates = [
568
+ (template_hydrostatic_pressure_at_depth, "hydrostatic_pressure_at_depth", "Easy"),
569
+ (template_basic_buoyant_force, "basic_buoyant_force", "Easy"),
570
+ (template_utube_manometer, "utube_manometer", "Intermediate"),
571
+ (template_floating_object_submersion_depth, "floating_object_submersion_depth", "Intermediate"),
572
+ (template_hydrostatic_force_on_plane, "hydrostatic_force_on_plane", "Advanced"),
573
+ ]
574
+
575
+ # List to store all generated problems
576
+ all_problems = []
577
+
578
+ # Generate problems for each template
579
+ for template_func, id_name, level in templates:
580
+ for _ in range(50):
581
+ # Generate a unique seed for each problem
582
+ seed = random.randint(1_000_000_000, 4_000_000_000)
583
+ random.seed(seed)
584
+
585
+ # Generate the problem and solution
586
+ question, solution = template_func()
587
+
588
+ # Create a JSON entry
589
+ problem_entry = {
590
+ "seed": seed,
591
+ "branch": "mechanical_engineering",
592
+ "domain": "fluid_mechanics",
593
+ "area": "fluid_statics",
594
+ "id": id_name,
595
+ "level": level,
596
+ "question": question,
597
+ "solution": solution
598
+ }
599
+
600
+ # Add to the list of problems
601
+ all_problems.append(problem_entry)
602
+
603
+ # Shuffle the problems to mix templates and levels
604
+ random.shuffle(all_problems)
605
+
606
+ # Write all problems to a .jsonl file
607
+ with open(output_file, "w") as file:
608
+ for problem in all_problems:
609
+ file.write(json.dumps(problem))
610
+ file.write("\n")
611
+
612
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
613
+
614
+
615
+ if __name__ == "__main__":
616
+ main()
data/templates/branches/mechanical_engineering/mechanics_of_materials/stress_and_strain.py ADDED
@@ -0,0 +1,916 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ from data.templates.branches.mechanical_engineering.constants import MATERIAL_PROPERTIES
4
+
5
+
6
+ # Template 1 (Easy)
7
+ def template_basic_stress_strain():
8
+ """
9
+ Basic Normal Stress and Strain
10
+
11
+ Scenario:
12
+ This template tests the fundamental definitions of normal stress (sigma) and
13
+ normal strain (epsilon). Given a simple prismatic bar with a known geometry
14
+ and an applied axial load, the user must calculate these two basic quantities.
15
+
16
+ Core Equations:
17
+ Normal Stress: sigma = P / A
18
+ Normal Strain: epsilon = delta / L
19
+
20
+ Returns:
21
+ tuple: A tuple containing:
22
+ - str: A question asking for the normal stress and strain.
23
+ - str: A step-by-step solution showing the calculations.
24
+ """
25
+ # 1. Parameterize inputs with random values, choosing a unit system first.
26
+ use_si_units = random.choice([True, False])
27
+ shape = random.choice(['circular', 'square'])
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
35
+
36
+ if shape == 'circular':
37
+ dimension_val = random.randint(20, 100) # diameter in mm
38
+ dimension_name = "diameter"
39
+ area = math.pi * (dimension_val / 2)**2 # mm²
40
+ dim_str = f"{dimension_val} mm"
41
+ else: # square
42
+ dimension_val = random.randint(20, 100) # side length in mm
43
+ dimension_name = "side length"
44
+ area = dimension_val**2 # mm²
45
+ dim_str = f"{dimension_val} mm"
46
+
47
+ load_str = f"{load} kN"
48
+ length_str = f"{length} m"
49
+ elongation_str = f"{elongation} mm"
50
+
51
+ # Core Calculations (using units for clarity: N, mm, MPa)
52
+ # 1 MPa = 1 N/mm^2
53
+ stress = (load * 1000) / area # Stress in MPa
54
+ # Strain (unitless, but convert units to be consistent)
55
+ # Elongation in mm, Length in m -> convert length to mm
56
+ strain = elongation / (length * 1000)
57
+
58
+ stress_unit = "MPa"
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
66
+
67
+ if shape == 'circular':
68
+ dimension_val = round(random.uniform(1.0, 5.0), 2) # diameter in inches
69
+ dimension_name = "diameter"
70
+ area = math.pi * (dimension_val / 2)**2 # in²
71
+ dim_str = f"{dimension_val} in"
72
+ else: # square
73
+ dimension_val = round(random.uniform(1.0, 5.0), 2) # side length in inches
74
+ dimension_name = "side length"
75
+ area = dimension_val**2
76
+ dim_str = f"{dimension_val} in"
77
+
78
+ load_str = f"{load} kips"
79
+ length_str = f"{length} in"
80
+ elongation_str = f"{elongation} in"
81
+
82
+ # Core Calculations (using units for clarity: kips, in, ksi)
83
+ # 1 ksi = 1 kip/in^2
84
+ stress = load / area # Stress in ksi
85
+ # Strain (unitless, since both length and elongation are in inches)
86
+ strain = elongation / length
87
+
88
+ stress_unit = "ksi"
89
+ strain_unit_explanation = "in/in or unitless"
90
+
91
+ # 2. Generate the question and solution strings
92
+ question = (
93
+ f"A prismatic bar with a length of {length_str} has a {shape} cross-section "
94
+ f"with a {dimension_name} of {dim_str}. The bar is subjected to an axial "
95
+ f"tensile load of {load_str}, causing it to elongate by {elongation_str}.\n\n"
96
+ f"Determine the following:\n"
97
+ f"a) The normal stress in the bar.\n"
98
+ f"b) The normal strain in the bar."
99
+ )
100
+
101
+ solution = (
102
+ f"**Given:**\n"
103
+ f"Load (P): {load_str}\n"
104
+ f"Length (L): {length_str}\n"
105
+ f"Elongation (delta): {elongation_str}\n"
106
+ f"Cross-Section: {shape.capitalize()} with {dimension_name} = {dim_str}\n\n"
107
+
108
+ f"**Step 1:** Calculate the Cross-Sectional Area (A)\n"
109
+ f"The area of a {shape} cross-section is calculated as follows:\n"
110
+ )
111
+
112
+ if shape == 'circular':
113
+ solution += (
114
+ f"A = pi * (d/2)^2 = pi * ({dimension_val}/2)^2 = {round(area, precision+1)} "
115
+ f"{'mm^2' if use_si_units else 'in^2'}\n\n"
116
+ )
117
+ else: # square
118
+ solution += (
119
+ f"A = side^2 = ({dimension_val})^2 = {round(area, precision+1)} "
120
+ f"{'mm^2' if use_si_units else 'in^2'}\n\n"
121
+ )
122
+
123
+ solution += (
124
+ f"**Step 2:** Calculate the Normal Stress (sigma)\n"
125
+ f"Normal stress is defined as the force per unit area: sigma = P / A.\n"
126
+ )
127
+
128
+ if use_si_units:
129
+ solution += (
130
+ f"To get the result in Megapascals (MPa), we use the load in Newtons (N) and the area in mm^2 (1 MPa = 1 N/mm^2).\n"
131
+ f"P = {load} kN = {load * 1000} N\n"
132
+ f"A = {round(area, precision+1)} mm^2\n"
133
+ f"sigma = ({load * 1000} N) / ({round(area, precision+1)} mm^2) = {round(stress, precision)} MPa\n\n"
134
+ )
135
+ else: # US units
136
+ solution += (
137
+ f"To get the result in kips per square inch (ksi), we use the load in kips and the area in in^2 (1 ksi = 1 kip/in^2).\n"
138
+ f"P = {load} kips\n"
139
+ f"A = {round(area, precision+1)} in^2\n"
140
+ f"sigma = ({load} kips) / ({round(area, precision+1)} in^2) = {round(stress, precision)} ksi\n\n"
141
+ )
142
+
143
+ solution += (
144
+ f"**Step 3:** Calculate the Normal Strain (epsilon)\n"
145
+ f"Normal strain is the change in length per unit of original length: epsilon = delta / L.\n"
146
+ f"It is a dimensionless quantity, but units must be consistent for calculation.\n"
147
+ )
148
+
149
+ if use_si_units:
150
+ solution += (
151
+ f"We convert the length from meters to millimeters to match the elongation units.\n"
152
+ f"delta = {elongation} mm\n"
153
+ f"L = {length} m = {length * 1000} mm\n"
154
+ f"epsilon = ({elongation} mm) / ({length * 1000} mm) = {strain:.3e}\n\n"
155
+ )
156
+ else: # US units
157
+ solution += (
158
+ f"Both elongation and length are already in inches, so we can divide directly.\n"
159
+ f"delta = {elongation} in\n"
160
+ f"L = {length} in\n"
161
+ f"epsilon = ({elongation} in) / ({length} in) = {strain:.3e}\n\n"
162
+ )
163
+
164
+ solution += (
165
+ f"**Answer:**\n"
166
+ f"a) Normal Stress (sigma) = **{round(stress, precision)} {stress_unit}**\n"
167
+ f"b) Normal Strain (epsilon) = **{strain:.3e}** ({strain_unit_explanation})"
168
+ )
169
+
170
+ return question, solution
171
+
172
+
173
+ # Template 2 (Easy)
174
+ def template_axial_deformation():
175
+ """
176
+ Axial Deformation (Hooke's Law)
177
+
178
+ Scenario:
179
+ This template combines stress, strain, and the material's Modulus of
180
+ Elasticity (E). It tests the ability to calculate the total elongation
181
+ or compression of a rod under an axial load. It can also be inverted
182
+ to find the maximum load for a given allowable deformation.
183
+
184
+ Core Equation:
185
+ Deformation: delta = (P * L) / (A * E)
186
+
187
+ Returns:
188
+ tuple: A tuple containing:
189
+ - str: A question about axial deformation or allowable load.
190
+ - str: A step-by-step solution showing the calculations.
191
+ """
192
+ # 1. Parameterize inputs with random values
193
+ use_si_units = random.choice([True, False])
194
+ shape = random.choice(['circular', 'square'])
195
+ material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
196
+ solve_for_load = random.choice([True, False]) # The variation
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
+
204
+ if shape == 'circular':
205
+ dimension_val = random.randint(25, 120) # diameter in mm
206
+ dimension_name = "diameter"
207
+ area = math.pi * (dimension_val / 2)**2
208
+ dim_str = f"{dimension_val} mm"
209
+ else: # square
210
+ dimension_val = random.randint(25, 120) # side in mm
211
+ dimension_name = "side length"
212
+ area = dimension_val**2
213
+ dim_str = f"{dimension_val} mm"
214
+
215
+ E_str = f"{material_E} GPa"
216
+ length_str = f"{length} m"
217
+
218
+ if solve_for_load:
219
+ # We are solving for P given delta_max
220
+ max_elongation = round(random.uniform(1.0, 6.0), 2) # in mm
221
+ max_elongation_str = f"{max_elongation} mm"
222
+ # Core Calculation: P = (delta * A * E) / L
223
+ # Units: mm * mm^2 * (N/mm^2) / mm = N
224
+ load_N = (max_elongation * area * (material_E * 1000)) / (length * 1000)
225
+ load = load_N / 1000 # convert to kN
226
+ load_str = f"{round(load, precision)} kN"
227
+ else:
228
+ # We are solving for delta given P
229
+ load = random.randint(50, 800) # in kN
230
+ load_str = f"{load} kN"
231
+ # Core Calculation: delta = (P * L) / (A * E)
232
+ # Units: (N * mm) / (mm^2 * N/mm^2) = mm
233
+ elongation = ((load * 1000) * (length * 1000)) / (area * (material_E * 1000))
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
+
241
+ if shape == 'circular':
242
+ dimension_val = round(random.uniform(0.5, 6.0), 2) # diameter in inches
243
+ dimension_name = "diameter"
244
+ area = math.pi * (dimension_val / 2)**2
245
+ dim_str = f"{dimension_val} in"
246
+ else: # square
247
+ dimension_val = round(random.uniform(0.5, 6.0), 2) # side in inches
248
+ dimension_name = "side length"
249
+ area = dimension_val**2
250
+ dim_str = f"{dimension_val} in"
251
+
252
+ E_str = f"{material_E} ksi"
253
+ length_str = f"{length} in"
254
+
255
+ if solve_for_load:
256
+ # Solving for P given delta_max
257
+ max_elongation = round(random.uniform(0.02, 0.2), 4) # in inches
258
+ max_elongation_str = f"{max_elongation} in"
259
+ # Core Calculation: P = (delta * A * E) / L
260
+ # Units: in * in^2 * (kips/in^2) / in = kips
261
+ load = (max_elongation * area * material_E) / length
262
+ load_str = f"{round(load, precision)} kips"
263
+ else:
264
+ # Solving for delta given P
265
+ load = random.randint(10, 200) # in kips
266
+ load_str = f"{load} kips"
267
+ # Core Calculation: delta = (P * L) / (A * E)
268
+ # Units: (kips * in) / (in^2 * kips/in^2) = in
269
+ elongation = (load * length) / (area * material_E)
270
+ elongation_str = f"{round(elongation, precision)} in"
271
+
272
+ # 2. Generate the question and solution strings
273
+ if solve_for_load:
274
+ question = (
275
+ f"A rod made of {material_name} has a length of {length_str} and a {shape} "
276
+ f"cross-section with a {dimension_name} of {dim_str}. The modulus of "
277
+ f"elasticity for {material_name} is {E_str}.\n\n"
278
+ f"What is the maximum allowable axial load the rod can support if the "
279
+ f"total elongation is not to exceed {max_elongation_str}?"
280
+ )
281
+
282
+ solution = (
283
+ f"**Given:**\n"
284
+ f"Material: {material_name} (E = {E_str})\n"
285
+ f"Length (L): {length_str}\n"
286
+ f"Cross-Section: {shape.capitalize()}, {dimension_name} = {dim_str}\n"
287
+ f"Maximum Elongation (delta_max): {max_elongation_str}\n\n"
288
+
289
+ f"**Step 1:** Calculate the Cross-Sectional Area (A)\n"
290
+ )
291
+ if shape == 'circular':
292
+ solution += (f" A = pi * (d/2)^2 = pi * ({dimension_val}/2)^2 = {round(area, precision+1)} {'mm^2' if use_si_units else 'in^2'}\n\n")
293
+ else: # square
294
+ solution += (f" A = side^2 = ({dimension_val})^2 = {round(area, precision+1)} {'mm^2' if use_si_units else 'in^2'}\n\n")
295
+
296
+ solution += (
297
+ f"**Step 2:** Calculate the Allowable Load (P)\n"
298
+ f"The formula for axial deformation is delta = (P * L) / (A * E).\n"
299
+ f"We can rearrange this to solve for the load P: P = (delta * A * E) / L.\n\n"
300
+ f"**Unit Conversion and Calculation:**\n"
301
+ )
302
+ if use_si_units:
303
+ solution += (
304
+ f"For consistency, we will use units of Newtons (N) and millimeters (mm).\n"
305
+ f"delta = {max_elongation} mm\n"
306
+ f"A = {round(area, precision+1)} mm^2\n"
307
+ f"E = {material_E} GPa = {material_E * 1000} MPa = {material_E * 1000} N/mm^2\n"
308
+ f"L = {length} m = {length * 1000} mm\n\n"
309
+ f"P = ({max_elongation} * {round(area, precision+1)} * {material_E * 1000}) / {length * 1000}\n"
310
+ f"P = {round(load_N, precision)} N = {round(load, precision)} kN\n\n"
311
+ )
312
+ else: # US units
313
+ solution += (
314
+ f"The units are already consistent (kips and inches).\n"
315
+ f"delta = {max_elongation} in\n"
316
+ f"A = {round(area, precision+1)} in^2\n"
317
+ f"E = {material_E} ksi\n"
318
+ f"L = {length} in\n\n"
319
+ f"P = ({max_elongation} * {round(area, precision+1)} * {material_E}) / {length}\n"
320
+ f"P = {round(load, precision)} kips\n\n"
321
+ )
322
+ solution += f"**Answer:**\n The maximum allowable load is **{load_str}**."
323
+ else: # solve_for_deformation
324
+ question = (
325
+ f"A {material_name} rod with a length of {length_str} is subjected to an axial "
326
+ f"tensile load of {load_str}. The rod has a {shape} cross-section with a "
327
+ f"{dimension_name} of {dim_str}. The modulus of elasticity for "
328
+ f"{material_name} is {E_str}.\n\n"
329
+ f"Calculate the total elongation of the rod."
330
+ )
331
+ solution = (
332
+ f"**Given:**\n"
333
+ f"Material: {material_name} (E = {E_str})\n"
334
+ f"Load (P): {load_str}\n"
335
+ f"Length (L): {length_str}\n"
336
+ f"Cross-Section: {shape.capitalize()}, {dimension_name} = {dim_str}\n\n"
337
+
338
+ f"**Step 1:** Calculate the Cross-Sectional Area (A)\n"
339
+ )
340
+ if shape == 'circular':
341
+ solution += (f" A = pi * (d/2)^2 = pi * ({dimension_val}/2)^2 = {round(area, precision+1)} {'mm^2' if use_si_units else 'in^2'}\n\n")
342
+ else: # square
343
+ solution += (f" A = side^2 = ({dimension_val})^2 = {round(area, precision+1)} {'mm^2' if use_si_units else 'in^2'}\n\n")
344
+
345
+ solution += (
346
+ f"**Step 2:** Calculate the Elongation (delta)\n"
347
+ f"The formula for axial deformation is delta = (P * L) / (A * E).\n\n"
348
+ f"**Unit Conversion and Calculation:**\n"
349
+ )
350
+ if use_si_units:
351
+ solution += (
352
+ f"For consistency, we will use units of Newtons (N) and millimeters (mm).\n"
353
+ f"P = {load} kN = {load * 1000} N\n"
354
+ f"L = {length} m = {length * 1000} mm\n"
355
+ f"A = {round(area, precision+1)} mm^2\n"
356
+ f"E = {material_E} GPa = {material_E * 1000} MPa = {material_E * 1000} N/mm^2\n\n"
357
+ f"delta = (({load * 1000}) * ({length * 1000})) / (({round(area, precision+1)}) * ({material_E * 1000}))\n"
358
+ f"delta = {round(elongation, precision)} mm\n\n"
359
+ )
360
+ else: # US units
361
+ solution += (
362
+ f"The units are already consistent (kips and inches).\n"
363
+ f"P = {load} kips\n"
364
+ f"L = {length} in\n"
365
+ f"A = {round(area, precision+1)} in^2\n"
366
+ f"E = {material_E} ksi\n\n"
367
+ f"delta = ({load} * {length}) / ({round(area, precision+1)} * {material_E})\n"
368
+ f"delta = {round(elongation, precision)} in\n\n"
369
+ )
370
+ solution += f"**Answer:**\n The total elongation of the rod is **{elongation_str}**."
371
+
372
+ return question, solution
373
+
374
+
375
+ # Template 3 (Intermediate)
376
+ def template_multi_segment_rod():
377
+ """
378
+ Deformation of a Multi-Segment Rod
379
+
380
+ Scenario:
381
+ This template involves a composite rod made of segments joined end-to-end and
382
+ fixed at one end. The segments can have different materials, lengths, or
383
+ cross-sectional areas. External loads are applied at the junctions. The user
384
+ must calculate the total deformation by summing the deformations of each segment,
385
+ which requires first finding the internal force in each section.
386
+
387
+ Core Equation:
388
+ Total Deformation: delta_total = sum(delta_i) = sum( (P_i * L_i) / (A_i * E_i) )
389
+
390
+ Returns:
391
+ tuple: A tuple containing:
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 = (
463
+ f"A composite rod, fixed at one end, consists of {num_segments} segments "
464
+ f"joined end-to-end as described below:\n"
465
+ )
466
+ for i, seg in enumerate(segments):
467
+ unit_L = "m" if use_si_units else "in"
468
+ question += (
469
+ f" - Segment {i+1}: Made of {seg['material_name']}, has a length of {seg['length']} {unit_L}, "
470
+ f"and a cross-section that is {seg['dim_str']}.\n"
471
+ )
472
+
473
+ question += f"\nThe rod is subjected to the following external axial loads (positive = tension, negative = compression):\n"
474
+ for i in range(num_segments):
475
+ node_index = i + 1
476
+ load_val = loads[node_index]
477
+ unit_P = "kN" if use_si_units else "kips"
478
+
479
+ if node_index < num_segments:
480
+ location_desc = f"at the junction between Segment {node_index} and {node_index+1}"
481
+ else:
482
+ location_desc = "at the free end"
483
+
484
+ question += f" - A load of {load_val} {unit_P} is applied {location_desc}.\n"
485
+
486
+ question += (
487
+ f"\nGiven the Moduli of Elasticity:\n"
488
+ )
489
+ unique_materials = {s['material_name'] for s in segments}
490
+ for mat in sorted(list(unique_materials)):
491
+ E_val = MATERIAL_PROPERTIES[mat]['E_GPa' if use_si_units else 'E_ksi']
492
+ unit_E = "GPa" if use_si_units else "ksi"
493
+ question += f" - E_{mat} = {E_val} {unit_E}\n"
494
+
495
+ question += f"\nDetermine the total deformation of the composite rod."
496
+
497
+ # Solution
498
+ solution = f"**Given:** The properties and loads as described in the question.\n\n"
499
+ solution += (
500
+ f"**Step 1:** Calculate the Internal Force (P) in Each Segment\n"
501
+ f"To find the internal force in any segment, we make a virtual 'cut' and sum all external forces acting on one side of the cut. We will work from the free end (right) to the fixed wall (left). A positive force indicates tension, and a negative force indicates compression.\n"
502
+ )
503
+ unit_P = "kN" if use_si_units else "kips"
504
+ for i in range(num_segments - 1, -1, -1):
505
+ seg_num = i + 1
506
+ internal_load = segments[i]['internal_load']
507
+ load_desc = " (Tension)" if internal_load > 0 else " (Compression)" if internal_load < 0 else " (No force)"
508
+
509
+ if seg_num == num_segments: # Last segment
510
+ solution += f" - **Segment {seg_num}:** P{seg_num} = {loads[seg_num]} {unit_P}{load_desc}\n"
511
+ else:
512
+ prev_load_desc = " (T)" if segments[i+1]['internal_load'] > 0 else " (C)" if segments[i+1]['internal_load'] < 0 else " (0)"
513
+ solution += f" - **Segment {seg_num}:** P{seg_num} = P{seg_num+1} + {loads[seg_num]} = {segments[i+1]['internal_load']}{prev_load_desc} + {loads[seg_num]} = {internal_load} {unit_P}{load_desc}\n"
514
+
515
+ solution += f"\nSummary of Internal Forces:\n"
516
+ for i, seg in enumerate(segments):
517
+ load_desc = " (Tension)" if seg['internal_load'] > 0 else " (Compression)"
518
+ solution += f" - P{i+1} = {seg['internal_load']} {unit_P}{load_desc}\n"
519
+
520
+ solution += (
521
+ f"\n**Step 2:** Calculate the Deformation (δ) of Each Segment\n"
522
+ f"Using the formula δ = (P × L) / (A × E) for each segment.\n"
523
+ )
524
+ unit_L = "m" if use_si_units else "in"
525
+ unit_D = "mm" if use_si_units else "in"
526
+ unit_E = "GPa" if use_si_units else "ksi"
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"
542
+ f"The total deformation is the algebraic sum of the individual segment deformations.\n"
543
+ f"δ_total = "
544
+ )
545
+ delta_sum_str = " + ".join([f"({round(s['deformation'], precision)})" for s in segments])
546
+ solution += delta_sum_str + "\n"
547
+ solution += f"δ_total = {round(total_deformation, precision)} {unit_D}\n\n"
548
+
549
+ final_desc = "elongation" if total_deformation > 0 else "contraction"
550
+ solution += (
551
+ f"**Answer:**\n"
552
+ f"The total deformation of the rod is **{round(total_deformation, precision)} {unit_D}** "
553
+ f"(a net {final_desc})."
554
+ )
555
+
556
+ return question, solution
557
+
558
+
559
+ # Template 4 (Intermediate)
560
+ def template_poissons_ratio():
561
+ """
562
+ Poisson's Ratio and Change in Diameter
563
+
564
+ Scenario:
565
+ This template tests the understanding of Poisson's Ratio (nu). A rod is
566
+ subjected to an axial load, causing it to elongate and contract laterally.
567
+ The user must calculate the change in the rod's diameter.
568
+
569
+ Core Equations:
570
+ Axial Strain: epsilon_axial = sigma_x / E = P / (A * E)
571
+ Lateral Strain: epsilon_lateral = -nu * epsilon_axial
572
+ Change in Diameter: delta_d = d_initial * epsilon_lateral
573
+
574
+ Returns:
575
+ tuple: A tuple containing:
576
+ - str: A question asking for the change in diameter.
577
+ - str: A step-by-step solution.
578
+ """
579
+ # 1. Parameterize inputs
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 = (
634
+ f"A solid circular rod made of {material_name} has an initial diameter of {dim_str}. "
635
+ f"The rod is subjected to an axial load of {load_str} ({load_type_str}).\n\n"
636
+ f"Given the material properties for {material_name}:\n"
637
+ f"Modulus of Elasticity (E) = {material['E_GPa' if use_si_units else 'E_ksi']} {'GPa' if use_si_units else 'ksi'}\n"
638
+ f"Poisson's Ratio (nu) = {material['nu']}\n\n"
639
+ f"Determine the following:\n"
640
+ f"a) The change in the rod's diameter.\n"
641
+ f"b) The final diameter of the rod under the load."
642
+ )
643
+
644
+ solution = (
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"
661
+ )
662
+ else: # US units
663
+ solution += (
664
+ f"sigma = P / A = {load_kips} kips / {round(area_in2, 4)} in^2 = {round(stress_ksi, 3)} ksi\n"
665
+ f"Now, calculate axial strain using Hooke's Law: epsilon_axial = sigma / E.\n"
666
+ f"epsilon_axial = {round(stress_ksi, 3)} ksi / {E_ksi} ksi = {axial_strain:.4e}\n\n"
667
+ )
668
+
669
+ solution += (
670
+ f"**Step 2:** Calculate Lateral Strain (epsilon_lateral)\n"
671
+ f"Lateral strain is related to axial strain by Poisson's ratio: epsilon_lateral = -nu * epsilon_axial.\n"
672
+ f"The negative sign indicates that for positive axial strain (elongation), the lateral strain is negative (contraction), and vice-versa.\n"
673
+ f"epsilon_lateral = -({nu}) * ({axial_strain:.4e}) = {lateral_strain:.4e}\n\n"
674
+
675
+ f"**Step 3:** Calculate the Change in Diameter (delta_d)\n"
676
+ f"The change in diameter is the lateral strain multiplied by the initial diameter.\n"
677
+ f"delta_d = d_0 * epsilon_lateral\n"
678
+ f"delta_d = ({initial_diameter_mm if use_si_units else initial_diameter_in} {unit_d}) * ({lateral_strain:.4e}) = {round(delta_diameter_mm if use_si_units else delta_diameter_in, precision)} {unit_d}\n\n"
679
+
680
+ f"**Step 4:** Calculate the Final Diameter (d_final)\n"
681
+ f"The final diameter is the initial diameter plus the change.\n"
682
+ f"d_final = d_0 + delta_d\n"
683
+ f"d_final = ({initial_diameter_mm if use_si_units else initial_diameter_in} {unit_d}) + ({round(delta_diameter_mm if use_si_units else delta_diameter_in, precision)} {unit_d}) = {round(final_diameter_mm if use_si_units else final_diameter_in, precision)} {unit_d}\n\n"
684
+
685
+ f"**Answer:**\n"
686
+ f"a) The change in diameter is **{round(delta_diameter_mm if use_si_units else delta_diameter_in, precision)} {unit_d}**.\n"
687
+ f"b) The final diameter is **{round(final_diameter_mm if use_si_units else final_diameter_in, precision)} {unit_d}**."
688
+ )
689
+
690
+ return question, solution
691
+
692
+
693
+ # Template 5 (Advanced)
694
+ def template_statically_indeterminate():
695
+ """
696
+ Statically Indeterminate Axially Loaded Member
697
+
698
+ Scenario:
699
+ A uniform rod is fixed between two rigid walls. An external load is applied
700
+ at an intermediate point. This is statically indeterminate because there are
701
+ two unknown reaction forces (at the walls) but only one static equilibrium
702
+ equation. The problem is solved by using a compatibility equation based on
703
+ the fact that the total deformation of the rod must be zero.
704
+
705
+ Core Equations:
706
+ Equilibrium: Sum(F_x) = 0 => R_A + R_C = P
707
+ Compatibility: delta_total = 0 => delta_AB + delta_BC = 0
708
+ (P_AB * L_AB / AE) + (P_BC * L_BC / AE) = 0
709
+
710
+ Returns:
711
+ tuple: A tuple containing:
712
+ - str: A question asking for reaction forces and stresses.
713
+ - str: A step-by-step solution.
714
+ """
715
+ # 1. Parameterize inputs
716
+ use_si_units = random.choice([True, False])
717
+ material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
718
+ material = MATERIAL_PROPERTIES[material_name]
719
+ shape = random.choice(['circular', 'square'])
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
727
+ load_P = random.randint(100, 500) # kN
728
+
729
+ if shape == 'circular':
730
+ diameter = random.randint(50, 150) # mm
731
+ area = math.pi * (diameter / 2)**2
732
+ dim_str = f"a diameter of {diameter} mm"
733
+ else: # square
734
+ side = random.randint(50, 150) # mm
735
+ area = side**2
736
+ dim_str = f"a side length of {side} mm"
737
+
738
+ E_val = material['E_GPa']
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
746
+ load_P = random.randint(50, 250) # kips
747
+
748
+ if shape == 'circular':
749
+ diameter = round(random.uniform(2.0, 6.0), 2) # inches
750
+ area = math.pi * (diameter / 2)**2
751
+ dim_str = f"a diameter of {diameter} in"
752
+ else: # square
753
+ side = round(random.uniform(2.0, 6.0), 2) # inches
754
+ area = side**2
755
+ dim_str = f"a side length of {side} in"
756
+
757
+ E_val = material['E_ksi']
758
+ unit_L, unit_P, unit_S, unit_A, unit_E = "in", "kips", "ksi", "in^2", "ksi"
759
+
760
+ # 2. Core Calculations
761
+ # From compatibility: R_A = P * L_BC / L_AC
762
+ # From equilibrium: R_C = P - R_A
763
+ R_A = load_P * (len_BC / total_length)
764
+ R_C = load_P - R_A
765
+
766
+ # Internal forces
767
+ P_AB = R_A # Tension
768
+ P_BC = -R_C # Compression
769
+
770
+ # Stresses
771
+ stress_AB = P_AB / area
772
+ stress_BC = P_BC / area
773
+
774
+ # Unit conversion for SI stress
775
+ if use_si_units:
776
+ stress_AB_MPa = stress_AB * 1000 # Convert kN/mm^2 to MPa
777
+ stress_BC_MPa = stress_BC * 1000 # Convert kN/mm^2 to MPa
778
+
779
+ # 3. Generate Question and Solution Strings
780
+ question = (
781
+ f"A solid {material_name} rod with a uniform {shape} cross-section is fixed at both ends, A and C. "
782
+ f"The rod has a total length of {total_length} {unit_L} and {dim_str}.\n\n"
783
+ f"An external axial load of {load_P} {unit_P} is applied to the right at point B, "
784
+ f"which is located at a distance of {len_AB} {unit_L} from end A.\n\n"
785
+ f"The Modulus of Elasticity for {material_name} is {E_val} {unit_E}.\n\n"
786
+ f"Determine the following:\n"
787
+ f"a) The reaction forces at supports A and C.\n"
788
+ f"b) The normal stress in segments AB and BC of the rod."
789
+ )
790
+
791
+ solution = (
792
+ f"**Given:**\n"
793
+ f"Total Length (L_AC): {total_length} {unit_L}\n"
794
+ f"Length of segment AB (L_AB): {len_AB} {unit_L}\n"
795
+ f"Length of segment BC (L_BC): {total_length} - {len_AB} = {round(len_BC, 2)} {unit_L}\n"
796
+ f"Applied Load (P): {load_P} {unit_P}\n"
797
+ f"Area (A): {round(area, 2)} {unit_A}\n"
798
+ f"Modulus of Elasticity (E): {E_val} {unit_E}\n\n"
799
+
800
+ f"This is a statically indeterminate problem because there are two unknown support reactions (R_A and R_C) and only one equation of static equilibrium.\n\n"
801
+
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"
809
+ f"delta_total = delta_AB + delta_BC = 0\n"
810
+ f"The internal force in segment AB is the reaction R_A (in tension, P_AB = R_A).\n"
811
+ f"The internal force in segment BC is the reaction R_C (in compression, P_BC = -R_C).\n\n"
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"
819
+ f"Substitute this into the simplified Equation 2:\n"
820
+ f"R_A * ({len_AB}) = ({load_P} - R_A) * ({round(len_BC, 2)})\n"
821
+ f"{round(len_AB, 2)}*R_A = {round(load_P * len_BC, 2)} - {round(len_BC, 2)}*R_A\n"
822
+ f"({round(len_AB, 2)} + {round(len_BC, 2)})*R_A = {round(load_P * len_BC, 2)}\n"
823
+ f"({total_length})*R_A = {round(load_P * len_BC, 2)}\n"
824
+ f"R_A = {round(load_P * len_BC, 2)} / {total_length} = {round(R_A, precision)} {unit_P}\n\n"
825
+ f"Now, find R_C using Equation 1:\n"
826
+ f"R_C = {load_P} - R_A = {load_P} - {round(R_A, precision)} = {round(R_C, precision)} {unit_P}\n\n"
827
+
828
+ f"**Step 4:** Calculate the Normal Stresses\n"
829
+ f"The stress in each segment is sigma = P_internal / A.\n"
830
+ f"**Segment AB:** The internal force is P_AB = R_A = {round(R_A, precision)} {unit_P} (Tension).\n"
831
+ )
832
+ if use_si_units:
833
+ solution += f"sigma_AB = ({round(R_A, precision)} kN) / ({round(area, 2)} mm^2) * 1000 = {round(stress_AB_MPa, precision)} MPa (Tension)\n"
834
+ else:
835
+ solution += f"sigma_AB = {round(R_A, precision)} kips / {round(area, 2)} in^2 = {round(stress_AB, precision)} ksi (Tension)\n"
836
+
837
+ solution += f" - **Segment BC:** The internal force is P_BC = -R_C = -{round(R_C, precision)} {unit_P} (Compression).\n"
838
+
839
+ if use_si_units:
840
+ solution += f"sigma_BC = (-{round(R_C, precision)} kN) / ({round(area, 2)} mm^2) * 1000 = {round(stress_BC_MPa, precision)} MPa (Compression)\n\n"
841
+ else:
842
+ solution += f"sigma_BC = -{round(R_C, precision)} kips / {round(area, 2)} in^2 = {round(stress_BC, precision)} ksi (Compression)\n\n"
843
+
844
+ solution += (
845
+ f"**Answer:**\n"
846
+ f"a) Reaction Forces: R_A = **{round(R_A, precision)} {unit_P}** and R_C = **{round(R_C, precision)} {unit_P}**.\n"
847
+ f"b) Normal Stresses: sigma_AB = **{round(stress_AB_MPa if use_si_units else stress_AB, precision)} {unit_S} (Tension)** and sigma_BC = **{round(abs(stress_BC_MPa if use_si_units else stress_BC), precision)} {unit_S} (Compression)**."
848
+ )
849
+ return question, solution
850
+
851
+
852
+ def main():
853
+ """
854
+ Generate numerous instances of each stres and strain - axial loading template
855
+ with different random seeds and write the results to a JSONL file.
856
+ """
857
+ import json
858
+ import os
859
+
860
+ # Define the output path (Modify this path according to where you are running the code from)
861
+ output_file = "testset/mechanical_engineering/mechanics_of_materials/stress_and_strain.jsonl"
862
+
863
+ # Create the directory if it doesn't exist
864
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
865
+
866
+ # List of template functions with their ID and level
867
+ templates = [
868
+ (template_basic_stress_strain, "basic_stress_strain", "Easy"),
869
+ (template_axial_deformation, "axial_deformation", "Easy"),
870
+ (template_multi_segment_rod, "multi_segment_rod", "Intermediate"),
871
+ (template_poissons_ratio, "poissons_ratio", "Intermediate"),
872
+ (template_statically_indeterminate, "statically_indeterminate", "Advanced"),
873
+ ]
874
+
875
+ # List to store all generated problems
876
+ all_problems = []
877
+
878
+ # Generate problems for each template
879
+ for template_func, id_name, level in templates:
880
+ for _ in range(50):
881
+ # Generate a unique seed for each problem
882
+ seed = random.randint(1_000_000_000, 4_000_000_000)
883
+ random.seed(seed)
884
+
885
+ # Generate the problem and solution
886
+ question, solution = template_func()
887
+
888
+ # Create a JSON entry
889
+ problem_entry = {
890
+ "seed": seed,
891
+ "branch": "mechanical_engineering",
892
+ "domain": "mechanics_of_materials",
893
+ "area": "stress_and_strain",
894
+ "id": id_name,
895
+ "level": level,
896
+ "question": question,
897
+ "solution": solution
898
+ }
899
+
900
+ # Add to the list of problems
901
+ all_problems.append(problem_entry)
902
+
903
+ # Shuffle the problems to mix templates and levels
904
+ random.shuffle(all_problems)
905
+
906
+ # Write all problems to a .jsonl file
907
+ with open(output_file, "w") as file:
908
+ for problem in all_problems:
909
+ file.write(json.dumps(problem))
910
+ file.write("\n")
911
+
912
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
913
+
914
+
915
+ if __name__ == "__main__":
916
+ main()
data/templates/branches/mechanical_engineering/mechanics_of_materials/torsion.py ADDED
@@ -0,0 +1,604 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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
+ """
8
+ Torsion: Shear Stress and Polar Moment of Inertia
9
+
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
18
+ J_solid = (pi / 2) * c^4
19
+ J_hollow = (pi / 2) * (c_outer^4 - c_inner^4)
20
+
21
+ Returns:
22
+ tuple: A tuple containing:
23
+ - str: A question asking for the maximum shearing stress in a shaft.
24
+ - str: A step-by-step solution to the problem.
25
+ """
26
+ # 1. Parameterize the inputs with random values
27
+ torque = round(random.uniform(100.0, 5000.0), 2) # Torque in N.m
28
+ shaft_type = random.choice(['solid', 'hollow'])
29
+
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
+
41
+ # Standardize precision for all calculations and outputs
42
+ precision = 3
43
+
44
+ # 2. Perform the core calculations for the solution
45
+
46
+ # Step A: Convert diameters to radii in meters
47
+ c_outer = d_outer / 2000.0 # Convert mm to m and get radius
48
+ if shaft_type == 'hollow':
49
+ c_inner = d_inner / 2000.0
50
+
51
+ # Step B: Calculate the polar moment of inertia (J)
52
+ if shaft_type == 'solid':
53
+ polar_moment_J = (math.pi / 2) * (c_outer ** 4)
54
+ j_calculation_str = f"J = (pi / 2) * c^4 = (pi / 2) * ({c_outer})^4 = {polar_moment_J:.3e} m^4"
55
+ else: # shaft_type == 'hollow'
56
+ polar_moment_J = (math.pi / 2) * (c_outer ** 4 - c_inner ** 4)
57
+ j_calculation_str = (
58
+ f"J = (pi / 2) * (c_outer^4 - c_inner^4)\n"
59
+ f" J = (pi / 2) * (({c_outer})^4 - ({c_inner})^4) = {polar_moment_J:.3e} m^4"
60
+ )
61
+
62
+ # Step C: Apply the torsion formula to find the stress in Pascals
63
+ # Note: 'c' in the formula refers to the outermost radius, c_outer.
64
+ tau_max_pascals = (torque * c_outer) / polar_moment_J
65
+
66
+ # Step D: Convert the final answer to megapascals (MPa)
67
+ tau_max_mpa = tau_max_pascals / 1e6
68
+
69
+ # 3. Generate the question and solution strings
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
+
80
+ question = (
81
+ f"A {geometry_desc} is subjected to a torque of {torque} N.m. "
82
+ f"Determine the maximum shearing stress in the shaft."
83
+ )
84
+
85
+ solution = (
86
+ f"**Given:**\n"
87
+ f"Torque (T) = {torque} N.m\n"
88
+ f"Shaft Type = {shaft_type.capitalize()}\n"
89
+ f"Outer Diameter (d_outer) = {d_outer} mm\n"
90
+ )
91
+ if shaft_type == 'hollow':
92
+ solution += f" - Inner Diameter (d_inner) = {d_inner} mm\n\n"
93
+ else:
94
+ solution += "\n"
95
+
96
+ solution += (
97
+ f"**Step 1:** Convert diameters to radii and express in meters.\n"
98
+ f"The maximum stress occurs at the outer surface, so we use the outer radius (c) for the stress calculation.\n"
99
+ f"Outer radius (c) = d_outer / 2 = {d_outer} / 2 = {d_outer/2.0} mm = {c_outer} m\n"
100
+ )
101
+ if shaft_type == 'hollow':
102
+ solution += f" - Inner radius (c_inner) = d_inner / 2 = {d_inner} / 2 = {d_inner/2.0} mm = {c_inner} m\n"
103
+ solution += "\n"
104
+
105
+ solution += (
106
+ f"**Step 2:** Calculate the polar moment of inertia (J) for the shaft's cross-section.\n"
107
+ f"For a {shaft_type} shaft:\n"
108
+ f" {j_calculation_str}\n\n"
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"
115
+ f"tau_max = ({torque} * {c_outer}) / {polar_moment_J:.3e}\n"
116
+ f"tau_max = {tau_max_pascals:.3e} Pa\n\n"
117
+
118
+ f"**Step 4:** Convert the stress from Pascals (Pa) to Megapascals (MPa).\n"
119
+ f"1 MPa = 1,000,000 Pa\n"
120
+ f"tau_max = {tau_max_pascals:.3e} Pa / 1e6 = {round(tau_max_mpa, precision)} MPa\n\n"
121
+
122
+ f"**Answer:**\n"
123
+ f"The maximum shearing stress in the shaft is {round(tau_max_mpa, precision)} MPa."
124
+ )
125
+
126
+ return question, solution
127
+
128
+
129
+ # Template 2 (Easy)
130
+ def template_angle_of_twist():
131
+ """
132
+ Torsion: Angle of Twist Calculation
133
+
134
+ Scenario:
135
+ This template assesses the ability to calculate the total angle of twist (phi)
136
+ for a uniform solid circular shaft. It requires a correct understanding of the
137
+ relationship between torque, length, material properties (Shear Modulus), and
138
+ the shaft's geometry (Polar Moment of Inertia).
139
+
140
+ Core Equations:
141
+ phi = (T * L) / (J * G)
142
+ J_solid = (pi / 2) * c^4
143
+
144
+ Returns:
145
+ tuple: A tuple containing:
146
+ - str: A question asking for the angle of twist in a shaft.
147
+ - str: A step-by-step solution to the problem.
148
+ """
149
+ # 1. Parameterize the inputs with random values
150
+ torque = round(random.uniform(500.0, 6000.0), 1) # Torque in N.m
151
+ length = round(random.uniform(0.5, 4.0), 2) # Length in m
152
+ diameter = random.randint(25, 100) # Diameter in mm
153
+
154
+ # Randomly select a material and its properties
155
+ material_name, shear_modulus_gpa = random.choice(list(SHEAR_MODULUS_VALUES.items()))
156
+
157
+ # Standardize precision for final outputs
158
+ precision = 4
159
+
160
+ # 2. Perform the core calculations for the solution
161
+
162
+ # Step A: Convert diameter (mm) to radius (m)
163
+ c_radius_m = diameter / 2000.0
164
+
165
+ # Step B: Calculate the polar moment of inertia (J)
166
+ polar_moment_J = (math.pi / 2) * (c_radius_m ** 4)
167
+
168
+ # Step C: Ensure all units are consistent (convert G from GPa to Pa)
169
+ shear_modulus_pa = shear_modulus_gpa * 1e9
170
+
171
+ # Step D: Apply the angle of twist formula to find the angle in radians
172
+ angle_rad = (torque * length) / (polar_moment_J * shear_modulus_pa)
173
+
174
+ # Step E: Convert the result from radians to degrees
175
+ angle_deg = math.degrees(angle_rad)
176
+
177
+ # 3. Generate the question and solution strings
178
+
179
+ question = (
180
+ f"A solid {material_name.lower()} shaft with a diameter of {diameter} mm and a length of {length} m "
181
+ f"is subjected to a torque of {torque} N.m. "
182
+ f"Given that the shear modulus (G) for {material_name.lower()} is {shear_modulus_gpa} GPa, "
183
+ f"calculate the total angle of twist. Provide the answer in both radians and degrees."
184
+ )
185
+
186
+ solution = (
187
+ f"**Given:**\n"
188
+ f"Torque (T) = {torque} N.m\n"
189
+ f"Length (L) = {length} m\n"
190
+ f"Diameter (d) = {diameter} mm\n"
191
+ f"Material = {material_name}\n"
192
+ f"Shear Modulus (G) = {shear_modulus_gpa} GPa\n\n"
193
+
194
+ f"**Step 1:** Convert the diameter to a radius in meters.\n"
195
+ f"Radius (c) = d / 2 = {diameter} / 2 = {diameter/2.0} mm = {c_radius_m} m\n\n"
196
+
197
+ f"**Step 2:** Calculate the polar moment of inertia (J) for the solid circular shaft.\n"
198
+ f"Formula: J = (pi / 2) * c^4\n"
199
+ f"J = (pi / 2) * ({c_radius_m})^4 = {polar_moment_J:.4e} m^4\n\n"
200
+
201
+ f"**Step 3:** Ensure consistent units for the angle of twist calculation.\n"
202
+ f"The shear modulus must be in Pascals (Pa) to be consistent with N and m.\n"
203
+ f"G = {shear_modulus_gpa} GPa = {shear_modulus_pa:.2e} Pa\n\n"
204
+
205
+ f"**Step 4:** Apply the angle of twist formula to find the angle in radians.\n"
206
+ f"Formula: phi = (T * L) / (J * G)\n"
207
+ f"phi = ({torque} * {length}) / ({polar_moment_J:.4e} * {shear_modulus_pa:.2e})\n"
208
+ f"phi = {round(angle_rad, precision)} radians\n\n"
209
+
210
+ f"**Step 5:** Convert the angle from radians to degrees.\n"
211
+ f"Angle in degrees = Angle in radians * (180 / pi)\n"
212
+ f"Angle = {round(angle_rad, precision)} * (180 / pi) = {round(angle_deg, precision)} degrees\n\n"
213
+
214
+ f"**Answer:**\n"
215
+ f"The total angle of twist is {round(angle_rad, precision)} radians, which is equivalent to "
216
+ f"{round(angle_deg, precision)} degrees."
217
+ )
218
+
219
+ return question, solution
220
+
221
+
222
+ # Template 3 (Intermediate)
223
+ def template_shaft_design_power():
224
+ """
225
+ Torsion: Power Transmission and Shaft Design
226
+
227
+ Scenario:
228
+ This template generates a classic design problem. It tests the ability to
229
+ first determine the torque on a shaft from its power and rotational speed,
230
+ and then use that torque to calculate the minimum required shaft diameter
231
+ based on an allowable shearing stress.
232
+
233
+ Core Equations:
234
+ P = 2 * pi * f * T
235
+ tau_allow = (T * c) / J
236
+ For a solid shaft, this simplifies to: c = ( (2 * T) / (pi * tau) )^(1/3)
237
+
238
+ Returns:
239
+ tuple: A tuple containing:
240
+ - str: A question asking for the minimum shaft diameter.
241
+ - str: A step-by-step solution to the design problem.
242
+ """
243
+ # 1. Parameterize the inputs with random values
244
+ power_kw = round(random.uniform(10.0, 500.0), 1)
245
+ allowable_stress_mpa = random.randint(40, 120)
246
+
247
+ # Randomly choose to provide frequency in Hz or RPM to add variety
248
+ use_rpm = random.choice([True, False])
249
+ if use_rpm:
250
+ frequency_rpm = random.randint(500, 3000)
251
+ frequency_hz = frequency_rpm / 60.0
252
+ frequency_str = f"{frequency_rpm} RPM"
253
+ else:
254
+ frequency_hz = random.randint(10, 100)
255
+ frequency_rpm = frequency_hz * 60
256
+ frequency_str = f"{frequency_hz} Hz"
257
+
258
+ # Standardize precision for final outputs
259
+ precision = 2
260
+
261
+ # 2. Perform the core calculations for the solution
262
+
263
+ # Step A: Convert primary units to base SI units (W, Pa, Hz)
264
+ power_w = power_kw * 1000
265
+ allowable_stress_pa = allowable_stress_mpa * 1e6
266
+
267
+ # Step B: Calculate the torque (T) on the shaft using the power formula
268
+ # P = 2 * pi * f * T => T = P / (2 * pi * f)
269
+ torque = power_w / (2 * math.pi * frequency_hz)
270
+
271
+ # Step C: Calculate the required radius (c) using the rearranged torsion formula
272
+ # tau = (T*c) / J = (T*c) / (pi/2 * c^4) = 2*T / (pi*c^3)
273
+ # c^3 = (2 * T) / (pi * tau)
274
+ c_cubed = (2 * torque) / (math.pi * allowable_stress_pa)
275
+ c_radius_m = c_cubed ** (1/3)
276
+
277
+ # Step D: Calculate the diameter in meters and then convert to millimeters
278
+ d_diameter_m = c_radius_m * 2
279
+ d_diameter_mm = d_diameter_m * 1000
280
+
281
+ # 3. Generate the question and solution strings
282
+
283
+ question = (
284
+ f"A motor is required to transmit {power_kw} kW of power at a rotational speed of {frequency_str}. "
285
+ f"If the solid circular shaft is to be made from a material with an allowable shearing stress of {allowable_stress_mpa} MPa, "
286
+ f"determine the minimum required diameter for the shaft."
287
+ )
288
+
289
+ solution = (
290
+ f"**Given:**\n"
291
+ f"Power (P) = {power_kw} kW\n"
292
+ f"Rotational Speed = {frequency_str}\n"
293
+ f"Allowable Shearing Stress (tau_allow) = {allowable_stress_mpa} MPa\n\n"
294
+
295
+ f"**Step 1:** Convert the given values to base SI units (Watts, Pascals, Hertz).\n"
296
+ f"Power (P) = {power_kw} kW = {power_w} W\n"
297
+ f"Allowable Stress (tau_allow) = {allowable_stress_mpa} MPa = {allowable_stress_pa:.1e} Pa\n"
298
+ )
299
+
300
+ if use_rpm:
301
+ solution += (
302
+ f" - Frequency (f) = {frequency_rpm} RPM = {frequency_rpm} / 60 = {round(frequency_hz, 2)} Hz\n\n"
303
+ )
304
+ else:
305
+ solution += f" - Frequency (f) = {frequency_hz} Hz\n\n"
306
+
307
+ solution += (
308
+ f"**Step 2:** Calculate the torque (T) exerted on the shaft.\n"
309
+ f"The relationship between power, torque, and frequency is P = 2 * pi * f * T.\n"
310
+ f"Rearranging for torque: T = P / (2 * pi * f)\n"
311
+ f"T = {power_w} / (2 * pi * {round(frequency_hz, 2)}) = {round(torque, 2)} N.m\n\n"
312
+
313
+ f"**Step 3:** Determine the required shaft radius (c) using the torsion formula.\n"
314
+ f"The formula for maximum stress in a solid shaft is tau = (T * c) / J, where J = (pi/2) * c^4.\n"
315
+ f"This simplifies to tau = 2 * T / (pi * c^3).\n"
316
+ f"Rearranging to solve for the radius: c^3 = (2 * T) / (pi * tau_allow)\n"
317
+ f"c^3 = (2 * {round(torque, 2)}) / (pi * {allowable_stress_pa:.1e}) = {c_cubed:.3e} m^3\n"
318
+ f"c = ({c_cubed:.3e})^(1/3) = {round(c_radius_m, 4)} m\n\n"
319
+
320
+ f"**Step 4:** Calculate the minimum diameter from the radius.\n"
321
+ f"Diameter (d) = 2 * c\n"
322
+ f"d = 2 * {round(c_radius_m, 4)} = {round(d_diameter_m, 4)} m\n"
323
+ f"In millimeters, d = {round(d_diameter_mm, precision)} mm\n\n"
324
+
325
+ f"**Answer:**\n"
326
+ f"The minimum required diameter for the solid circular shaft is {round(d_diameter_mm, precision)} mm."
327
+ )
328
+
329
+ return question, solution
330
+
331
+
332
+ # Template 4 (Intermediate)
333
+ def template_composite_shafts_series():
334
+ """
335
+ Torsion: Composite Shafts in Series
336
+
337
+ Scenario:
338
+ This problem involves a shaft made of two different segments joined end-to-end.
339
+ It tests the understanding that the total angle of twist at the free end is
340
+ the algebraic sum of the angles of twist of each individual segment.
341
+
342
+ Core Equations:
343
+ phi_total = sum( (T_i * L_i) / (J_i * G_i) )
344
+ For this case: phi_total = phi_1 + phi_2
345
+ J_solid = (pi / 2) * c^4
346
+
347
+ Returns:
348
+ tuple: A tuple containing:
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
+
386
+ # 3. Generate the question and solution strings
387
+
388
+ question = (
389
+ f"A composite shaft consists of two segments, AB and BC, rigidly connected at B. "
390
+ f"Segment AB is a solid {mat1_name.lower()} shaft of length {l1} m and diameter {d1} mm. "
391
+ f"Segment BC is a solid {mat2_name.lower()} shaft of length {l2} m and diameter {d2} mm. "
392
+ f"The shaft is fixed at end A, and a torque of {torque} N.m is applied at the free end C. "
393
+ f"Calculate the total angle of twist at end C. "
394
+ f"(Use G = {g1_gpa} GPa for {mat1_name.lower()} and G = {g2_gpa} GPa for {mat2_name.lower()})."
395
+ )
396
+
397
+ solution = (
398
+ f"**Given:**\n"
399
+ f"Applied Torque (T) = {torque} N.m\n"
400
+ f"Segment AB: L1={l1} m, d1={d1} mm, Material={mat1_name} (G1={g1_gpa} GPa)\n"
401
+ f"Segment BC: L2={l2} m, d2={d2} mm, Material={mat2_name} (G2={g2_gpa} GPa)\n\n"
402
+
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"
410
+ f"Polar Moment of Inertia (J1) = (pi/2) * c1^4 = (pi/2) * ({c1_m})^4 = {j1:.3e} m^4\n"
411
+ f"Angle of Twist (phi_AB) = (T * L1) / (J1 * G1)\n"
412
+ f"phi_AB = ({torque} * {l1}) / ({j1:.3e} * {g1_pa:.2e}) = {round(phi1_rad, precision + 1)} radians\n\n"
413
+
414
+ f"**Step 3:** Calculate Angle of Twist for Segment BC (phi_BC)\n"
415
+ f"Convert units: c2 = {d2}/2 mm = {c2_m} m; G2 = {g2_gpa} GPa = {g2_pa:.2e} Pa\n"
416
+ f"Polar Moment of Inertia (J2) = (pi/2) * c2^4 = (pi/2) * ({c2_m})^4 = {j2:.3e} m^4\n"
417
+ f"Angle of Twist (phi_BC) = (T * L2) / (J2 * G2)\n"
418
+ f"phi_BC = ({torque} * {l2}) / ({j2:.3e} * {g2_pa:.2e}) = {round(phi2_rad, precision + 1)} radians\n\n"
419
+
420
+ f"**Step 4:** Calculate Total Angle of Twist at End C\n"
421
+ f"phi_total = phi_AB + phi_BC\n"
422
+ f"phi_total = {round(phi1_rad, precision + 1)} + {round(phi2_rad, precision + 1)} = {round(phi_total_rad, precision)} radians\n"
423
+ f"To convert to degrees: Angle_deg = Angle_rad * (180 / pi)\n"
424
+ f"phi_total = {round(phi_total_rad, precision)} * (180 / pi) = {round(phi_total_deg, precision)} degrees\n\n"
425
+
426
+ f"**Answer:**\n"
427
+ f"The total angle of twist at the free end C is {round(phi_total_rad, precision)} radians, "
428
+ f"or {round(phi_total_deg, precision)} degrees."
429
+ )
430
+
431
+ return question, solution
432
+
433
+
434
+ # Template 5 (Advanced)
435
+ def template_statically_indeterminate_shaft():
436
+ """
437
+ Torsion: Statically Indeterminate Shaft
438
+
439
+ Scenario:
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)
483
+
484
+ # 3. Generate the question and solution strings
485
+
486
+ question = (
487
+ f"A solid {material_name.lower()} shaft of diameter {diameter} mm and length {total_length} m is "
488
+ f"fixed at both ends, A and B. A torque of {int(applied_torque)} N.m is applied at point C, "
489
+ f"located {pos_L_AC} m from end A. The shear modulus for the material is {shear_modulus_gpa} GPa. "
490
+ f"Determine the reaction torques at the fixed supports, T_A and T_B."
491
+ )
492
+
493
+ solution = (
494
+ f"**Given:**\n"
495
+ f"Total Length (L) = {total_length} m\n"
496
+ f"Applied Torque (T_applied) = {int(applied_torque)} N.m\n"
497
+ f"Location of Torque from A (L_AC) = {pos_L_AC} m\n"
498
+ f"Diameter (d) = {diameter} mm, Material = {material_name}\n\n"
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
538
+
539
+
540
+ def main():
541
+ """
542
+ Generate numerous instances of each torsion template
543
+ with different random seeds and write the results to a JSONL file.
544
+ """
545
+ import json
546
+ import os
547
+
548
+ # Define the output path (Modify this path according to where you are running the code from)
549
+ output_file = "testset/mechanical_engineering/mechanics_of_materials/torsion.jsonl"
550
+
551
+ # Create the directory if it doesn't exist
552
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
553
+
554
+ # List of template functions with their ID and level
555
+ templates = [
556
+ (template_shear_stress_torsion, "shear_stress_torsion", "Easy"),
557
+ (template_angle_of_twist, "angle_of_twist", "Easy"),
558
+ (template_shaft_design_power, "shaft_design_power", "Intermediate"),
559
+ (template_composite_shafts_series, "composite_shafts_series", "Intermediate"),
560
+ (template_statically_indeterminate_shaft, "statically_indeterminate_shaft", "Advanced"),
561
+ ]
562
+
563
+ # List to store all generated problems
564
+ all_problems = []
565
+
566
+ # Generate problems for each template
567
+ for template_func, id_name, level in templates:
568
+ for _ in range(50):
569
+ # Generate a unique seed for each problem
570
+ seed = random.randint(1_000_000_000, 4_000_000_000)
571
+ random.seed(seed)
572
+
573
+ # Generate the problem and solution
574
+ question, solution = template_func()
575
+
576
+ # Create a JSON entry
577
+ problem_entry = {
578
+ "seed": seed,
579
+ "branch": "mechanical_engineering",
580
+ "domain": "mechanics_of_materials",
581
+ "area": "torsion",
582
+ "id": id_name,
583
+ "level": level,
584
+ "question": question,
585
+ "solution": solution
586
+ }
587
+
588
+ # Add to the list of problems
589
+ all_problems.append(problem_entry)
590
+
591
+ # Shuffle the problems to mix templates and levels
592
+ random.shuffle(all_problems)
593
+
594
+ # Write all problems to a .jsonl file
595
+ with open(output_file, "w") as file:
596
+ for problem in all_problems:
597
+ file.write(json.dumps(problem))
598
+ file.write("\n")
599
+
600
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
601
+
602
+
603
+ if __name__ == "__main__":
604
+ main()
data/templates/branches/mechanical_engineering/vibrations_and_acoustics/harmonically_excited_vibrations.py ADDED
@@ -0,0 +1,584 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+
4
+
5
+ # Template 1 (Easy)
6
+ def template_system_properties():
7
+ """
8
+ Vibrations: System Properties (Natural Frequency and Damping Ratio)
9
+
10
+ Scenario:
11
+ This template generates a foundational problem for analyzing a single-degree-of-freedom
12
+ spring-mass-damper system. Given the system's physical parameters (mass, stiffness,
13
+ and damping coefficient), the objective is to calculate its key dynamic properties:
14
+ the undamped natural frequency, the critical damping coefficient, and the damping ratio.
15
+ Finally, the system is classified based on its damping level.
16
+
17
+ Core Equations:
18
+ omega_n = sqrt(k / m)
19
+ c_cr = 2 * sqrt(k * m)
20
+ zeta = c / c_cr
21
+
22
+ Returns:
23
+ tuple: A tuple containing:
24
+ - str: A question asking for the system's dynamic properties and classification.
25
+ - str: A step-by-step solution to the problem.
26
+ """
27
+ # 1. Parameterize the inputs with random values
28
+
29
+ # Mass in kg, ensuring a practical range
30
+ mass = round(random.uniform(2.0, 250.0), 2)
31
+
32
+ # Stiffness in N/m
33
+ stiffness = round(random.uniform(1000.0, 150000.0), 1)
34
+
35
+ # To generate diverse and meaningful scenarios (underdamped, critically damped, overdamped),
36
+ # we first determine the critical damping and then set the actual damping based on it.
37
+
38
+ # We select a random damping ratio first to define the system type
39
+ target_zeta = round(random.uniform(0.1, 2.5), 3)
40
+
41
+ # Handle the edge case of m=0 or k=0, though randomization range prevents it.
42
+ if mass <= 0 or stiffness <= 0:
43
+ # Assign default safe values in the unlikely event of non-positive inputs
44
+ mass = 10.0
45
+ stiffness = 20000.0
46
+
47
+ critical_damping = 2 * math.sqrt(stiffness * mass)
48
+
49
+ # Calculate the actual damping coefficient based on the target zeta
50
+ damping_coeff = round(target_zeta * critical_damping, 2)
51
+
52
+ # Standardize precision for all calculations and outputs
53
+ precision = 4
54
+
55
+ # 2. Perform the core calculations for the solution
56
+
57
+ # Step A: Calculate the undamped natural frequency (omega_n)
58
+ omega_n = math.sqrt(stiffness / mass)
59
+
60
+ # Step B: The critical damping coefficient (c_cr) was already calculated
61
+ # We re-calculate here to show the step clearly in the solution.
62
+ c_critical = 2 * math.sqrt(stiffness * mass)
63
+
64
+ # Step C: Calculate the damping ratio (zeta)
65
+ damping_ratio = damping_coeff / c_critical
66
+
67
+ # Step D: Classify the system based on the damping ratio
68
+ if abs(damping_ratio - 1.0) < 1e-9: # Use tolerance for floating point comparison
69
+ system_type = "critically damped"
70
+ elif damping_ratio < 1.0:
71
+ system_type = "underdamped"
72
+ else:
73
+ system_type = "overdamped"
74
+
75
+ # 3. Generate the question and solution strings
76
+
77
+ question = (
78
+ f"A spring-mass-damper system has the following properties:\n"
79
+ f"Mass (m) = {mass} kg\n"
80
+ f"Spring Stiffness (k) = {stiffness} N/m\n"
81
+ f"Damping Coefficient (c) = {damping_coeff} N.s/m\n\n"
82
+ f"Determine the following:\n"
83
+ f" 1. The undamped natural frequency (in rad/s).\n"
84
+ f" 2. The critical damping coefficient.\n"
85
+ f" 3. The damping ratio.\n"
86
+ f" 4. Classify the system as underdamped, critically damped, or overdamped."
87
+ )
88
+
89
+ solution = (
90
+ f"**Given:**\n"
91
+ f"Mass (m) = {mass} kg\n"
92
+ f"Stiffness (k) = {stiffness} N/m\n"
93
+ f"Damping Coefficient (c) = {damping_coeff} N.s/m\n\n"
94
+
95
+ f"**Step 1:** Calculate the undamped natural frequency (omega_n).\n"
96
+ f"The formula is: omega_n = sqrt(k / m)\n"
97
+ f"omega_n = sqrt({stiffness} / {mass})\n"
98
+ f"omega_n = {round(omega_n, precision)} rad/s\n\n"
99
+
100
+ f"**Step 2:** Calculate the critical damping coefficient (c_cr).\n"
101
+ f"The formula is: c_cr = 2 * sqrt(k * m)\n"
102
+ f"c_cr = 2 * sqrt({stiffness} * {mass})\n"
103
+ f"c_cr = {round(c_critical, precision)} N.s/m\n\n"
104
+
105
+ f"**Step 3:** Calculate the damping ratio (zeta).\n"
106
+ f"The formula is: zeta = c / c_cr\n"
107
+ f"zeta = {damping_coeff} / {round(c_critical, precision)}\n"
108
+ f"zeta = {round(damping_ratio, precision)}\n\n"
109
+
110
+ f"**Step 4:** Classify the system based on the damping ratio.\n"
111
+ f"The damping ratio is {round(damping_ratio, precision)}.\n"
112
+ f"Since zeta is {'less than 1' if system_type == 'underdamped' else ('equal to 1' if system_type == 'critically damped' else 'greater than 1')}, "
113
+ f"the system is classified as **{system_type}**.\n\n"
114
+
115
+ f"**Answer:**\n"
116
+ f"Undamped Natural Frequency: {round(omega_n, precision)} rad/s\n"
117
+ f"Critical Damping Coefficient: {round(c_critical, precision)} N.s/m\n"
118
+ f"Damping Ratio: {round(damping_ratio, precision)}\n"
119
+ f"System Type: {system_type.capitalize()}"
120
+ )
121
+
122
+ return question, solution
123
+
124
+
125
+ # Template 2 (Intermediate)
126
+ def template_rotating_unbalance():
127
+ """
128
+ Vibrations: Response to Rotating Unbalance
129
+
130
+ Scenario:
131
+ This template generates a problem involving a machine with a rotating component
132
+ that is out of balance. This common engineering scenario creates a harmonic
133
+ excitation force whose magnitude is dependent on the operating speed. The goal
134
+ is to determine the resulting steady-state vibration amplitude.
135
+
136
+ Core Equations:
137
+ omega_n = sqrt(k / m)
138
+ zeta = c / (2 * sqrt(k * m))
139
+ r = omega / omega_n
140
+ F_0 = m_e * e * omega^2
141
+ X = F_0 / sqrt((k - m * omega^2)^2 + (c * omega)^2)
142
+
143
+ Returns:
144
+ tuple: A tuple containing:
145
+ - str: A question asking for the steady-state amplitude of vibration.
146
+ - str: A step-by-step solution to the problem.
147
+ """
148
+ # 1. Parameterize the inputs with random values
149
+
150
+ # Total mass of the machine in kg
151
+ m_total = round(random.uniform(50.0, 500.0), 1)
152
+
153
+ # Eccentric mass in kg (should be a small fraction of total mass)
154
+ m_eccentric = round(random.uniform(0.1, m_total * 0.05), 2)
155
+
156
+ # Eccentricity in mm
157
+ eccentricity_mm = random.randint(10, 150)
158
+
159
+ # Stiffness in N/m
160
+ stiffness = round(random.uniform(5e4, 2e6), 0)
161
+
162
+ # Define system properties by choosing a damping ratio first for better control
163
+ # Lightly damped systems are common for this problem type.
164
+ damping_ratio_zeta = round(random.uniform(0.05, 0.6), 3)
165
+
166
+ # Define operating speed by choosing a frequency ratio first
167
+ # This ensures we get a good spread of cases (below, near, and above resonance)
168
+ freq_ratio_r = round(random.uniform(0.3, 3.0), 3)
169
+
170
+ # Standardize precision for final outputs
171
+ precision = 5
172
+
173
+ # 2. Perform the core calculations for the solution
174
+
175
+ # Handle potential edge cases from inputs
176
+ if m_total <= 0 or stiffness <= 0:
177
+ return "Error: Mass and stiffness must be positive.", "Invalid input parameters."
178
+
179
+ # Step A: Calculate fundamental system properties
180
+ omega_n = math.sqrt(stiffness / m_total)
181
+ c_critical = 2 * math.sqrt(stiffness * m_total)
182
+ damping_coeff = damping_ratio_zeta * c_critical
183
+
184
+ # Step B: Determine the operating speed from the chosen frequency ratio
185
+ omega = freq_ratio_r * omega_n
186
+ operating_speed_rpm = omega * 60 / (2 * math.pi)
187
+
188
+ # Step C: Convert units for calculation consistency
189
+ eccentricity_m = eccentricity_mm / 1000.0
190
+
191
+ # Step D: Calculate the magnitude of the unbalanced force
192
+ force_magnitude_F0 = m_eccentric * eccentricity_m * (omega ** 2)
193
+
194
+ # Step E: Calculate the steady-state amplitude (X) in meters
195
+ # Denominator components
196
+ term1 = stiffness - m_total * (omega ** 2)
197
+ term2 = damping_coeff * omega
198
+ denominator = math.sqrt(term1**2 + term2**2)
199
+
200
+ amplitude_m = force_magnitude_F0 / denominator if denominator != 0 else float('inf')
201
+
202
+ # Step F: Convert final amplitude to millimeters for a more intuitive answer
203
+ amplitude_mm = amplitude_m * 1000.0
204
+
205
+ # 3. Generate the question and solution strings
206
+
207
+ question = (
208
+ f"A machine with a total mass of {m_total} kg is supported by a spring and damper system. "
209
+ f"The system has an equivalent stiffness of {stiffness:,.0f} N/m and an equivalent damping "
210
+ f"coefficient of {round(damping_coeff, 2)} N.s/m.\n\n"
211
+ f"The machine contains a rotating component that has an unbalance equivalent to a mass of "
212
+ f"{m_eccentric} kg located at an eccentricity of {eccentricity_mm} mm. "
213
+ f"If the machine operates at a speed of {round(operating_speed_rpm, 0):,.0f} RPM, "
214
+ f"determine the steady-state amplitude of vibration."
215
+ )
216
+
217
+ solution = (
218
+ f"**Given:**\n"
219
+ f"Total Mass (m) = {m_total} kg\n"
220
+ f"Stiffness (k) = {stiffness:,.0f} N/m\n"
221
+ f"Damping Coefficient (c) = {round(damping_coeff, 2)} N.s/m\n"
222
+ f"Eccentric Mass (m_e) = {m_eccentric} kg\n"
223
+ f"Eccentricity (e) = {eccentricity_mm} mm = {eccentricity_m} m\n"
224
+ f"Operating Speed = {round(operating_speed_rpm, 0):,.0f} RPM\n\n"
225
+
226
+ f"**Step 1:** Convert the operating speed from RPM to rad/s.\n"
227
+ f"omega = (Speed in RPM) * (2 * pi / 60)\n"
228
+ f"omega = {round(operating_speed_rpm, 0):,.0f} * (2 * pi / 60) = {round(omega, precision)} rad/s\n\n"
229
+
230
+ f"**Step 2:** Calculate the system's natural frequency (omega_n) and damping ratio (zeta).\n"
231
+ f"omega_n = sqrt(k / m) = sqrt({stiffness:,.0f} / {m_total}) = {round(omega_n, precision)} rad/s\n"
232
+ f"c_cr = 2 * sqrt(k * m) = 2 * sqrt({stiffness:,.0f} * {m_total}) = {round(c_critical, precision)} N.s/m\n"
233
+ f"zeta = c / c_cr = {round(damping_coeff, 2)} / {round(c_critical, precision)} = {round(damping_ratio_zeta, precision)}\n\n"
234
+
235
+ f"**Step 3:** Calculate the magnitude of the unbalanced force (F_0).\n"
236
+ f"The force from a rotating unbalance is given by F_0 = m_e * e * omega^2.\n"
237
+ f"F_0 = {m_eccentric} kg * {eccentricity_m} m * ({round(omega, precision)} rad/s)^2\n"
238
+ f"F_0 = {round(force_magnitude_F0, precision)} N\n\n"
239
+
240
+ f"**Step 4:** Calculate the steady-state amplitude of vibration (X).\n"
241
+ f"The formula for amplitude is: X = F_0 / sqrt((k - m * omega^2)^2 + (c * omega)^2)\n"
242
+ f"Numerator = F_0 = {round(force_magnitude_F0, precision)} N\n"
243
+ f"Denominator Part 1: (k - m * omega^2) = ({stiffness:,.0f} - {m_total} * {round(omega, precision)}^2) = {round(term1, precision)}\n"
244
+ f"Denominator Part 2: (c * omega) = ({round(damping_coeff, 2)} * {round(omega, precision)}) = {round(term2, precision)}\n"
245
+ f"Denominator = sqrt(({round(term1, precision)})^2 + ({round(term2, precision)})^2) = {round(denominator, precision)}\n"
246
+ f"X = {round(force_magnitude_F0, precision)} / {round(denominator, precision)}\n"
247
+ f"X = {round(amplitude_m, precision + 2)} m\n\n"
248
+
249
+ f"**Step 5:** Convert the amplitude to millimeters.\n"
250
+ f"Amplitude in mm = {round(amplitude_m, precision + 2)} m * 1000 mm/m = {round(amplitude_mm, precision)} mm\n\n"
251
+
252
+ f"**Answer:**\n"
253
+ f"The steady-state amplitude of vibration is **{round(amplitude_mm, 3)} mm**."
254
+ )
255
+
256
+ return question, solution
257
+
258
+
259
+ # Template 3 (Intermediate)
260
+ def template_vibration_transmissibility():
261
+ """
262
+ Vibrations: Displacement Transmissibility from Base Excitation
263
+
264
+ Scenario:
265
+ This template addresses the problem of a system mounted on a vibrating foundation.
266
+ It is a key concept in vibration isolation, where the goal is to minimize the
267
+ motion transmitted from a vibrating source to a sensitive component. The template
268
+ calculates the ratio of the output amplitude to the input (base) amplitude
269
+ and the absolute amplitude of the system.
270
+
271
+ Core Equations:
272
+ omega_n = sqrt(k / m)
273
+ zeta = c / (2 * sqrt(k * m))
274
+ r = omega / omega_n
275
+ TR = X / Y = sqrt( (1 + (2*zeta*r)^2) / ( (1 - r^2)^2 + (2*zeta*r)^2 ) )
276
+
277
+ Returns:
278
+ tuple: A tuple containing:
279
+ - str: A question asking for the transmissibility and absolute amplitude.
280
+ - str: A step-by-step solution to the problem.
281
+ """
282
+ # 1. Parameterize the inputs with random values
283
+
284
+ # Mass of the sensitive instrument in kg
285
+ mass = round(random.uniform(5.0, 150.0), 1)
286
+
287
+ # Stiffness of the isolation mount in N/m
288
+ stiffness = round(random.uniform(2e3, 5e5), 0)
289
+
290
+ # Amplitude of the base vibration in mm
291
+ base_amplitude_Y_mm = round(random.uniform(0.1, 8.0), 2)
292
+
293
+ # Use frequency ratio and damping ratio to control the problem's outcome
294
+ # This ensures we test conditions of amplification (r~1) and isolation (r > sqrt(2))
295
+ freq_ratio_r = round(random.uniform(0.2, 5.0), 3)
296
+ damping_ratio_zeta = round(random.uniform(0.05, 0.7), 3)
297
+
298
+ # Standardize precision for final outputs
299
+ precision = 4
300
+
301
+ # 2. Perform the core calculations for the solution
302
+
303
+ # Handle potential edge cases
304
+ if mass <= 0 or stiffness <= 0:
305
+ return "Error: Mass and stiffness must be positive.", "Invalid input parameters."
306
+
307
+ # Step A: Calculate fundamental system properties
308
+ omega_n = math.sqrt(stiffness / mass)
309
+ c_critical = 2 * math.sqrt(stiffness * mass)
310
+ damping_coeff = damping_ratio_zeta * c_critical
311
+
312
+ # Step B: Determine the base excitation frequency from the chosen frequency ratio
313
+ omega = freq_ratio_r * omega_n
314
+ base_freq_hz = omega / (2 * math.pi)
315
+
316
+ # Step C: Convert base amplitude to meters for calculation
317
+ base_amplitude_Y_m = base_amplitude_Y_mm / 1000.0
318
+
319
+ # Step D: Calculate the displacement transmissibility ratio (TR)
320
+ tr_num = 1 + (2 * damping_ratio_zeta * freq_ratio_r)**2
321
+ tr_den = (1 - freq_ratio_r**2)**2 + (2 * damping_ratio_zeta * freq_ratio_r)**2
322
+
323
+ # Avoid division by zero, although highly unlikely with these random ranges
324
+ if tr_den == 0:
325
+ transmissibility_ratio = float('inf')
326
+ else:
327
+ transmissibility_ratio = math.sqrt(tr_num / tr_den)
328
+
329
+ # Step E: Calculate the absolute amplitude of the instrument's vibration
330
+ amplitude_X_m = transmissibility_ratio * base_amplitude_Y_m
331
+ amplitude_X_mm = amplitude_X_m * 1000.0
332
+
333
+ # 3. Generate the question and solution strings
334
+
335
+ question = (
336
+ f"A sensitive instrument of mass {mass} kg is supported by an isolation mount. "
337
+ f"The mount has an effective stiffness of {stiffness:,.0f} N/m and provides a damping "
338
+ f"ratio of {damping_ratio_zeta}.\n\n"
339
+ f"The foundation on which the instrument is placed is vibrating harmonically at a frequency of "
340
+ f"{round(base_freq_hz, 2)} Hz with an amplitude of {base_amplitude_Y_mm} mm.\n\n"
341
+ f"Determine:\n"
342
+ f"1. The displacement transmissibility ratio.\n"
343
+ f"2. The absolute amplitude of vibration of the instrument in millimeters."
344
+ )
345
+
346
+ solution = (
347
+ f"**Given:**\n"
348
+ f"Mass (m) = {mass} kg\n"
349
+ f"Stiffness (k) = {stiffness:,.0f} N/m\n"
350
+ f"Damping Ratio (zeta) = {damping_ratio_zeta}\n"
351
+ f"Base Vibration Frequency (f) = {round(base_freq_hz, 2)} Hz\n"
352
+ f"Base Vibration Amplitude (Y) = {base_amplitude_Y_mm} mm = {base_amplitude_Y_m} m\n\n"
353
+
354
+ f"**Step 1:** Calculate the system's undamped natural frequency (omega_n).\n"
355
+ f"omega_n = sqrt(k / m) = sqrt({stiffness:,.0f} / {mass}) = {round(omega_n, precision)} rad/s\n\n"
356
+
357
+ f"**Step 2:** Convert the base vibration frequency to rad/s and find the frequency ratio (r).\n"
358
+ f"Base frequency (omega) = f * 2 * pi = {round(base_freq_hz, 2)} * 2 * pi = {round(omega, precision)} rad/s\n"
359
+ f"Frequency ratio (r) = omega / omega_n = {round(omega, precision)} / {round(omega_n, precision)} = {round(freq_ratio_r, precision)}\n\n"
360
+
361
+ f"**Step 3:** Calculate the displacement transmissibility ratio (TR).\n"
362
+ f"The formula is: TR = sqrt( (1 + (2*zeta*r)^2) / ( (1 - r^2)^2 + (2*zeta*r)^2 ) )\n"
363
+ f"Let's calculate the terms:\n"
364
+ f"r = {round(freq_ratio_r, precision)}\n"
365
+ f"zeta = {damping_ratio_zeta}\n"
366
+ f"Numerator = 1 + (2 * {damping_ratio_zeta} * {round(freq_ratio_r, precision)})^2 = {round(tr_num, precision)}\n"
367
+ f"Denominator = (1 - ({round(freq_ratio_r, precision)})^2)^2 + (2 * {damping_ratio_zeta} * {round(freq_ratio_r, precision)})^2 = {round(tr_den, precision)}\n"
368
+ f"TR = sqrt({round(tr_num, precision)} / {round(tr_den, precision)}) = {round(transmissibility_ratio, precision)}\n\n"
369
+
370
+ f"**Step 4:** Calculate the absolute amplitude of the instrument (X).\n"
371
+ f"The relationship is X = TR * Y.\n"
372
+ f"X = {round(transmissibility_ratio, precision)} * {base_amplitude_Y_mm} mm\n"
373
+ f"X = {round(amplitude_X_mm, precision)} mm\n\n"
374
+
375
+ f"**Answer:**\n"
376
+ f"The displacement transmissibility ratio is **{round(transmissibility_ratio, 3)}**.\n"
377
+ f"The absolute amplitude of the instrument's vibration is **{round(amplitude_X_mm, 3)} mm**."
378
+ )
379
+
380
+ return question, solution
381
+
382
+
383
+ # Template 4 (Advanced)
384
+ def template_vibration_isolator_design():
385
+ """
386
+ Vibrations: Vibration Isolator Design (Inverse Problem)
387
+
388
+ Scenario:
389
+ This template creates an advanced design problem. An engine generating a harmonic
390
+ force needs to be mounted on isolators to limit the force transmitted to its
391
+ foundation. Given a maximum allowable force transmission percentage, the task
392
+ is to determine the necessary stiffness of the isolation system. This is an
393
+ inverse problem, requiring algebraic manipulation to solve for a system parameter.
394
+
395
+ Core Equations:
396
+ TR = sqrt( (1 + (2*zeta*r)^2) / ( (1 - r^2)^2 + (2*zeta*r)^2 ) )
397
+ This is solved for r, which is then used to find omega_n and finally k.
398
+ k = m * omega_n^2
399
+
400
+ Returns:
401
+ tuple: A tuple containing:
402
+ - str: A question asking for the required stiffness of an isolator.
403
+ - str: A step-by-step solution to the design problem.
404
+ """
405
+ # 1. Parameterize the inputs with random values
406
+
407
+ # Mass of the engine in kg
408
+ mass = round(random.uniform(100.0, 2000.0), 1)
409
+
410
+ # Operating speed in RPM
411
+ operating_speed_rpm = random.randint(500, 3000)
412
+
413
+ # Desired force transmissibility in percent
414
+ transmissibility_percent = random.randint(5, 20)
415
+
416
+ # Assumed damping ratio for the isolators (typically low)
417
+ damping_ratio_zeta = round(random.uniform(0.05, 0.25), 3)
418
+
419
+ # Standardize precision for final outputs
420
+ precision = 4
421
+
422
+ # 2. Perform the core calculations for the solution
423
+
424
+ # Step A: Convert inputs to consistent units
425
+ omega = operating_speed_rpm * (2 * math.pi / 60)
426
+ transmissibility_ratio_TR = transmissibility_percent / 100.0
427
+
428
+ # Step B: Solve for the frequency ratio (r)
429
+ # The equation TR^2 = (1 + (2*zeta*r)^2) / ((1-r^2)^2 + (2*zeta*r)^2)
430
+ # rearranges into a quadratic equation in terms of r^2: A*(r^2)^2 + B*(r^2) + C = 0
431
+ TR_sq = transmissibility_ratio_TR**2
432
+
433
+ A = TR_sq
434
+ B = 4 * (damping_ratio_zeta**2) * (TR_sq - 1) - 2 * TR_sq
435
+ C = TR_sq - 1
436
+
437
+ # Calculate the discriminant
438
+ discriminant = B**2 - 4 * A * C
439
+
440
+ # Ensure a real solution exists (handles complex roots)
441
+ if discriminant < 0:
442
+ # This case is highly unlikely with TR < 1, but it's good practice to handle it.
443
+ return ("Error: No real solution for frequency ratio (complex roots).",
444
+ "The design parameters are not physically achievable.")
445
+
446
+ # Solve the quadratic equation for r^2
447
+ r_sq_sol1 = (-B + math.sqrt(discriminant)) / (2 * A)
448
+ r_sq_sol2 = (-B - math.sqrt(discriminant)) / (2 * A)
449
+
450
+ # For effective isolation (transmissibility TR < 1), the frequency ratio 'r'
451
+ # must be greater than sqrt(2). We therefore need the larger, positive root for r^2.
452
+ r_squared = max(r_sq_sol1, r_sq_sol2)
453
+
454
+ # Add validation check for non-physical results (negative roots for r^2)
455
+ if r_squared < 0:
456
+ return ("Error: Design parameters result in a non-physical solution (r^2 < 0).",
457
+ "The required transmissibility cannot be achieved with the given damping.")
458
+
459
+ freq_ratio_r = math.sqrt(r_squared)
460
+
461
+ # Step C: Calculate the required natural frequency (omega_n)
462
+ omega_n_req = omega / freq_ratio_r
463
+
464
+ # Step D: Calculate the required stiffness (k)
465
+ stiffness_req = mass * (omega_n_req**2)
466
+
467
+ # 3. Generate the question and solution strings
468
+
469
+ question = (
470
+ f"An engine with a mass of {mass} kg operates at a constant speed of {operating_speed_rpm} RPM. "
471
+ f"It needs to be mounted on a set of vibration isolators. The design specification requires that no more than "
472
+ f"{transmissibility_percent}% of the engine's unbalanced force is transmitted to the foundation.\n\n"
473
+ f"Assuming the isolators have a combined damping ratio of {damping_ratio_zeta}, "
474
+ f"determine the total required stiffness (k) of the isolation system."
475
+ )
476
+
477
+ solution = (
478
+ f"**Given:**\n"
479
+ f"Mass (m) = {mass} kg\n"
480
+ f"Operating Speed = {operating_speed_rpm} RPM\n"
481
+ f"Maximum Transmissibility (TR) = {transmissibility_percent}% = {transmissibility_ratio_TR}\n"
482
+ f"Damping Ratio (zeta) = {damping_ratio_zeta}\n\n"
483
+
484
+ f"**Step 1:** Convert the operating speed to rad/s.\n"
485
+ f"omega = {operating_speed_rpm} RPM * (2 * pi / 60) = {round(omega, precision)} rad/s\n\n"
486
+
487
+ f"**Step 2:** Set up the force transmissibility equation to solve for the frequency ratio (r).\n"
488
+ f"The formula is TR^2 = [1 + (2*zeta*r)^2] / [(1 - r^2)^2 + (2*zeta*r)^2]\n"
489
+ f"Rearranging this gives a quadratic equation in the form A(r^2)^2 + B(r^2) + C = 0.\n"
490
+ f"A = TR^2 = {round(TR_sq, precision)}\n"
491
+ f"B = 4*zeta^2*(TR^2 - 1) - 2*TR^2 = 4*({damping_ratio_zeta}^2)*({round(TR_sq, precision)} - 1) - 2*{round(TR_sq, precision)} = {round(B, precision)}\n"
492
+ f"C = TR^2 - 1 = {round(TR_sq, precision)} - 1 = {round(C, precision)}\n\n"
493
+
494
+ f"**Step 3:** Solve the quadratic equation for r^2 using the formula r^2 = (-B +/- sqrt(B^2 - 4AC)) / 2A.\n"
495
+ f"Discriminant (D) = B^2 - 4AC = ({round(B, precision)})^2 - 4*({round(A, precision)})*({round(C, precision)}) = {round(discriminant, precision)}\n"
496
+ f"The two solutions for r^2 are: {round(r_sq_sol1, precision)} and {round(r_sq_sol2, precision)}.\n"
497
+ f"For effective vibration isolation, the frequency ratio 'r' must be greater than sqrt(2) (approx 1.414). This requires us to select the larger of the two positive solutions for r^2.\n"
498
+ f"Required r^2 = {round(r_squared, precision)}\n\n"
499
+
500
+ f"**Step 4:** Calculate the required frequency ratio (r) and validate the isolation condition.\n"
501
+ f"r = sqrt({round(r_squared, precision)}) = {round(freq_ratio_r, precision)}\n"
502
+ f"Check: Is r > sqrt(2)? Yes, {round(freq_ratio_r, precision)} > 1.414. The condition for isolation is met.\n\n"
503
+
504
+ f"**Step 5:** Determine the required natural frequency (omega_n) of the system.\n"
505
+ f"Since r = omega / omega_n, the required omega_n = omega / r.\n"
506
+ f"omega_n = {round(omega, precision)} / {round(freq_ratio_r, precision)} = {round(omega_n_req, precision)} rad/s\n\n"
507
+
508
+ f"**Step 6:** Calculate the total required stiffness (k).\n"
509
+ f"The natural frequency is defined by omega_n = sqrt(k / m). Therefore, k = m * omega_n^2.\n"
510
+ f"k = {mass} kg * ({round(omega_n_req, precision)} rad/s)^2\n"
511
+ f"k = {round(stiffness_req, 0):,.0f} N/m\n\n"
512
+
513
+ f"**Answer:**\n"
514
+ f"The total required stiffness for the isolation system is **{round(stiffness_req, 0):,.0f} N/m**.\n\n"
515
+ f"*Note: In a practical application, this total stiffness would be distributed among several individual isolator mounts.*"
516
+ )
517
+
518
+ return question, solution
519
+
520
+
521
+ def main():
522
+ """
523
+ Generate numerous instances of each free harmonically excited vibrations
524
+ template with different random seeds and write the results to a JSONL file.
525
+ """
526
+ import json
527
+ import os
528
+
529
+ # Define the output path (Modify this path according to where you are running the code from)
530
+ output_file = "testset/mechanical_engineering/vibrations_and_acoustics/harmonically_excited_vibrations.jsonl"
531
+
532
+ # Create the directory if it doesn't exist
533
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
534
+
535
+ # List of template functions with their ID and level
536
+ templates = [
537
+ (template_system_properties, "system_properties", "Easy"),
538
+ (template_rotating_unbalance, "rotating_unbalance", "Intermediate"),
539
+ (template_vibration_transmissibility, "vibration_transmissibility", "Intermediate"),
540
+ (template_vibration_isolator_design, "vibration_isolator_design", "Advanced"),
541
+ ]
542
+
543
+ # List to store all generated problems
544
+ all_problems = []
545
+
546
+ # Generate problems for each template
547
+ for template_func, id_name, level in templates:
548
+ for _ in range(50):
549
+ # Generate a unique seed for each problem
550
+ seed = random.randint(1_000_000_000, 4_000_000_000)
551
+ random.seed(seed)
552
+
553
+ # Generate the problem and solution
554
+ question, solution = template_func()
555
+
556
+ # Create a JSON entry
557
+ problem_entry = {
558
+ "seed": seed,
559
+ "branch": "mechanical_engineering",
560
+ "domain": "vibrations_and_acoustics",
561
+ "area": "harmonically_excited_vibrations",
562
+ "id": id_name,
563
+ "level": level,
564
+ "question": question,
565
+ "solution": solution
566
+ }
567
+
568
+ # Add to the list of problems
569
+ all_problems.append(problem_entry)
570
+
571
+ # Shuffle the problems to mix templates and levels
572
+ random.shuffle(all_problems)
573
+
574
+ # Write all problems to a .jsonl file
575
+ with open(output_file, "w") as file:
576
+ for problem in all_problems:
577
+ file.write(json.dumps(problem))
578
+ file.write("\n")
579
+
580
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
581
+
582
+
583
+ if __name__ == "__main__":
584
+ main()
data/templates/branches/mechanical_engineering/vibrations_and_acoustics/single_degree_of_freedom_systems.py ADDED
@@ -0,0 +1,655 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+
4
+
5
+ # Template 1 (Easy)
6
+ def template_undamped_natural_frequency_translational():
7
+ """
8
+ Free Vibration: Undamped Natural Frequency of a Translational System
9
+
10
+ Scenario:
11
+ This template generates a fundamental problem on a simple spring-mass
12
+ system. It tests the ability to calculate the key characteristics of
13
+ its undamped free vibration: the natural frequency in both rad/s (omega_n)
14
+ and Hertz (f_n), and the natural period (tau_n).
15
+
16
+ Core Equations:
17
+ omega_n = sqrt(k / m)
18
+ f_n = omega_n / (2 * pi)
19
+ tau_n = 1 / f_n
20
+
21
+ Returns:
22
+ tuple: A tuple containing:
23
+ - str: A question asking for the system's natural frequencies and period.
24
+ - str: A step-by-step solution to the problem.
25
+ """
26
+ # 1. Parameterize the inputs with random values for high diversity
27
+
28
+ # Mass (m): Chosen from a wide range to represent different scales,
29
+ # from small mechanical parts to larger objects.
30
+ mass = round(random.uniform(0.5, 750.0), 2) # in kg
31
+
32
+ # Stiffness (k): Integer values representing a typical range for mechanical springs.
33
+ stiffness = random.randint(500, 250000) # in N/m
34
+
35
+ # Standardize precision for all calculations and final outputs
36
+ precision = 3
37
+
38
+ # 2. Perform the core calculations for the solution
39
+
40
+ # Step A: Calculate the undamped natural frequency in radians per second
41
+ omega_n = math.sqrt(stiffness / mass)
42
+
43
+ # Step B: Convert the natural frequency from rad/s to Hertz (Hz)
44
+ f_n = omega_n / (2 * math.pi)
45
+
46
+ # Step C: Calculate the natural period of oscillation
47
+ tau_n = 1 / f_n
48
+
49
+ # 3. Generate the question and solution strings
50
+
51
+ question = (
52
+ f"An undamped single-degree-of-freedom system consists of a mass of {mass} kg "
53
+ f"and a spring with a stiffness of {stiffness} N/m. "
54
+ f"Determine the system's undamped natural frequency in both radians per second (rad/s) and Hertz (Hz). "
55
+ f"Also, calculate the natural period of vibration."
56
+ )
57
+
58
+ solution = (
59
+ f"**Given:**\n"
60
+ f"Mass (m) = {mass} kg\n"
61
+ f"Spring Stiffness (k) = {stiffness} N/m\n\n"
62
+
63
+ f"**Step 1:** Calculate the undamped natural frequency in radians per second (omega_n).\n"
64
+ f"The formula is: omega_n = sqrt(k / m)\n"
65
+ f"omega_n = sqrt({stiffness} / {mass})\n"
66
+ f"omega_n = {round(omega_n, precision)} rad/s\n\n"
67
+
68
+ f"**Step 2:** Convert the natural frequency to Hertz (f_n).\n"
69
+ f"The formula is: f_n = omega_n / (2 * pi)\n"
70
+ f"f_n = {round(omega_n, precision)} / (2 * pi)\n"
71
+ f"f_n = {round(f_n, precision)} Hz\n\n"
72
+
73
+ f"**Step 3:** Calculate the natural period of vibration (tau_n).\n"
74
+ f"The period is the reciprocal of the frequency in Hz.\n"
75
+ f"The formula is: tau_n = 1 / f_n\n"
76
+ f"tau_n = 1 / {round(f_n, precision)}\n"
77
+ f"tau_n = {round(tau_n, precision)} s\n\n"
78
+
79
+ f"**Answer:**\n"
80
+ f"The undamped natural frequency is {round(omega_n, precision)} rad/s or {round(f_n, precision)} Hz.\n"
81
+ f"The natural period of vibration is {round(tau_n, precision)} seconds."
82
+ )
83
+
84
+ return question, solution
85
+
86
+
87
+ # Template 2 (Easy)
88
+ def template_undamped_natural_frequency_torsional():
89
+ """
90
+ Free Vibration: Undamped Natural Frequency of a Torsional System
91
+
92
+ Scenario:
93
+ This template generates a problem for a simple torsional system, which
94
+ typically consists of a disc or flywheel attached to a shaft. It is the
95
+ rotational equivalent of the spring-mass system. The goal is to calculate
96
+ the natural frequency and period of torsional oscillation.
97
+
98
+ Core Equations:
99
+ omega_n = sqrt(k_t / J_0)
100
+ f_n = omega_n / (2 * pi)
101
+ tau_n = 1 / f_n
102
+
103
+ Returns:
104
+ tuple: A tuple containing:
105
+ - str: A question asking for the system's torsional natural frequencies and period.
106
+ - str: A step-by-step solution to the problem.
107
+ """
108
+ # 1. Parameterize the inputs with random values
109
+
110
+ # Mass moment of inertia (J_0): Represents the rotational inertia of the disc.
111
+ # The range covers small rotors to medium-sized flywheels.
112
+ mass_moment_of_inertia = round(random.uniform(0.05, 25.0), 3) # in kg-m^2
113
+
114
+ # Torsional stiffness (k_t): Represents the shaft's resistance to twisting.
115
+ torsional_stiffness = random.randint(200, 75000) # in N-m/rad
116
+
117
+ # Standardize precision for all calculations and final outputs
118
+ precision = 3
119
+
120
+ # 2. Perform the core calculations for the solution
121
+
122
+ # Step A: Calculate the undamped natural frequency in radians per second
123
+ omega_n = math.sqrt(torsional_stiffness / mass_moment_of_inertia)
124
+
125
+ # Step B: Convert the natural frequency from rad/s to Hertz (Hz)
126
+ f_n = omega_n / (2 * math.pi)
127
+
128
+ # Step C: Calculate the natural period of oscillation
129
+ tau_n = 1 / f_n
130
+
131
+ # 3. Generate the question and solution strings
132
+
133
+ question = (
134
+ f"A torsional pendulum consists of a disc with a mass moment of inertia of "
135
+ f"{mass_moment_of_inertia} kg-m^2 attached to a shaft with a torsional stiffness of "
136
+ f"{torsional_stiffness} N-m/rad. The system is undamped. "
137
+ f"Calculate the natural frequency of torsional vibration in both rad/s and Hz, "
138
+ f"and determine the corresponding period."
139
+ )
140
+
141
+ solution = (
142
+ f"**Given:**\n"
143
+ f"Mass Moment of Inertia (J_0) = {mass_moment_of_inertia} kg-m^2\n"
144
+ f"Torsional Stiffness (k_t) = {torsional_stiffness} N-m/rad\n\n"
145
+
146
+ f"**Step 1:** Calculate the undamped natural frequency in radians per second (omega_n).\n"
147
+ f"The formula for a torsional system is: omega_n = sqrt(k_t / J_0)\n"
148
+ f"omega_n = sqrt({torsional_stiffness} / {mass_moment_of_inertia})\n"
149
+ f"omega_n = {round(omega_n, precision)} rad/s\n\n"
150
+
151
+ f"**Step 2:** Convert the natural frequency to Hertz (f_n).\n"
152
+ f"The relationship is: f_n = omega_n / (2 * pi)\n"
153
+ f"f_n = {round(omega_n, precision)} / (2 * pi)\n"
154
+ f"f_n = {round(f_n, precision)} Hz\n\n"
155
+
156
+ f"**Step 3:** Calculate the natural period of vibration (tau_n).\n"
157
+ f"The period is the reciprocal of the frequency in Hz.\n"
158
+ f"The formula is: tau_n = 1 / f_n\n"
159
+ f"tau_n = 1 / {round(f_n, precision)}\n"
160
+ f"tau_n = {round(tau_n, precision)} s\n\n"
161
+
162
+ f"**Answer:**\n"
163
+ f"The undamped torsional natural frequency is {round(omega_n, precision)} rad/s or {round(f_n, precision)} Hz.\n"
164
+ f"The natural period of vibration is {round(tau_n, precision)} seconds."
165
+ )
166
+
167
+ return question, solution
168
+
169
+
170
+ # Template 3 (Intermediate)
171
+ def template_undamped_response_initial_conditions():
172
+ """
173
+ Free Vibration: Response of an Undamped System to Initial Conditions
174
+
175
+ Scenario:
176
+ This template assesses the ability to determine the specific equation
177
+ of motion, x(t), for an undamped spring-mass system given a set of
178
+ initial conditions (displacement and velocity). This requires first
179
+ finding the natural frequency and then solving for the two constants
180
+ in the general solution.
181
+
182
+ Core Equations:
183
+ omega_n = sqrt(k / m)
184
+ General Solution: x(t) = A1 * cos(omega_n * t) + A2 * sin(omega_n * t)
185
+ From initial conditions: A1 = x(0) and A2 = v(0) / omega_n
186
+
187
+ Returns:
188
+ tuple: A tuple containing:
189
+ - str: A question asking for the equation of motion.
190
+ - str: A step-by-step solution deriving the equation.
191
+ """
192
+ # 1. Parameterize the inputs with random values
193
+ mass = round(random.uniform(1.0, 500.0), 2) # in kg
194
+ stiffness = random.randint(1000, 200000) # in N/m
195
+
196
+ # Randomize initial conditions, including cases where one might be zero.
197
+ # Displacement is given in mm for the question, velocity in m/s.
198
+ initial_disp_mm = 0
199
+ initial_vel_ms = 0.0
200
+
201
+ # Use a chooser to create varied scenarios
202
+ # 1: Only displacement, 2: Only velocity, 3: Both are non-zero
203
+ scenario_choice = random.randint(1, 3)
204
+ if scenario_choice == 1:
205
+ initial_disp_mm = random.randint(-100, 100)
206
+ while initial_disp_mm == 0: initial_disp_mm = random.randint(-100, 100)
207
+ elif scenario_choice == 2:
208
+ initial_vel_ms = round(random.uniform(-5.0, 5.0), 2)
209
+ while initial_vel_ms == 0.0: initial_vel_ms = round(random.uniform(-5.0, 5.0), 2)
210
+ else: # scenario_choice == 3
211
+ initial_disp_mm = random.randint(-100, 100)
212
+ initial_vel_ms = round(random.uniform(-5.0, 5.0), 2)
213
+ while initial_disp_mm == 0: initial_disp_mm = random.randint(-100, 100)
214
+ while initial_vel_ms == 0.0: initial_vel_ms = round(random.uniform(-5.0, 5.0), 2)
215
+
216
+ # Convert initial displacement from mm to meters for calculations
217
+ initial_disp_m = initial_disp_mm / 1000.0
218
+
219
+ precision = 4
220
+
221
+ # 2. Perform the core calculations for the solution
222
+
223
+ # Step A: Calculate the natural frequency
224
+ omega_n = math.sqrt(stiffness / mass)
225
+
226
+ # Step B: Determine the constants A1 and A2 from initial conditions
227
+ A1 = initial_disp_m
228
+ A2 = initial_vel_ms / omega_n
229
+
230
+ # 3. Generate the question and solution strings
231
+
232
+ question = (
233
+ f"An undamped spring-mass system has a mass of {mass} kg and a spring stiffness of {stiffness} N/m. "
234
+ f"The mass is given an initial displacement of {initial_disp_mm} mm and an initial velocity of {initial_vel_ms} m/s. "
235
+ f"Determine the equation of motion, x(t), for the system."
236
+ )
237
+
238
+ solution = (
239
+ f"**Given:**\n"
240
+ f"Mass (m) = {mass} kg\n"
241
+ f"Stiffness (k) = {stiffness} N/m\n"
242
+ f"Initial Displacement (x(0)) = {initial_disp_mm} mm = {initial_disp_m} m\n"
243
+ f"Initial Velocity (v(0)) = {initial_vel_ms} m/s\n\n"
244
+
245
+ f"**Step 1:** Calculate the undamped natural frequency (omega_n).\n"
246
+ f"omega_n = sqrt(k / m) = sqrt({stiffness} / {mass}) = {round(omega_n, precision)} rad/s\n\n"
247
+
248
+ f"**Step 2:** State the general form of the solution for an undamped system.\n"
249
+ f"The general solution is: x(t) = A1 * cos(omega_n * t) + A2 * sin(omega_n * t)\n\n"
250
+
251
+ f"**Step 3:** Apply the initial conditions to find the constants A1 and A2.\n"
252
+ f"First, apply the initial displacement at t=0:\n"
253
+ f"x(0) = A1 * cos(0) + A2 * sin(0) = A1\n"
254
+ f"Therefore, A1 = x(0) = {initial_disp_m} m\n\n"
255
+
256
+ f"Next, find the derivative of x(t) to get the velocity, v(t):\n"
257
+ f"v(t) = dx/dt = -A1 * omega_n * sin(omega_n * t) + A2 * omega_n * cos(omega_n * t)\n"
258
+ f"Now apply the initial velocity at t=0:\n"
259
+ f"v(0) = -A1 * omega_n * sin(0) + A2 * omega_n * cos(0) = A2 * omega_n\n"
260
+ f"Therefore, A2 = v(0) / omega_n = {initial_vel_ms} / {round(omega_n, precision)} = {round(A2, precision)} m\n\n"
261
+
262
+ f"**Step 4:** Substitute the constants and omega_n into the general solution.\n"
263
+ f"x(t) = {round(A1, precision)} * cos({round(omega_n, precision)} * t) + ({round(A2, precision)}) * sin({round(omega_n, precision)} * t)\n\n"
264
+ )
265
+
266
+ # Clean up the final equation for better readability
267
+ cos_term = ""
268
+ if abs(A1) > 1e-9: # Use a small tolerance to handle floating point inaccuracies
269
+ cos_term = f"{round(A1, precision)}*cos({round(omega_n, precision)}*t)"
270
+
271
+ sin_term = ""
272
+ if abs(A2) > 1e-9:
273
+ sign = "-" if A2 < 0 else "+"
274
+ # If the cos_term is empty, don't start with a plus sign
275
+ if not cos_term and A2 > 0:
276
+ sign = ""
277
+
278
+ sin_term = f" {sign} {abs(round(A2, precision))}*sin({round(omega_n, precision)}*t)"
279
+ # Remove leading space if it's the first term
280
+ if not cos_term:
281
+ sin_term = sin_term.lstrip()
282
+
283
+ final_equation = cos_term + sin_term
284
+ if not final_equation: final_equation = "0"
285
+
286
+ solution += (
287
+ f"**Answer:**\n"
288
+ f"The final equation of motion is:\n"
289
+ f"x(t) = {final_equation} (m)"
290
+ )
291
+
292
+ return question, solution
293
+
294
+
295
+ # Template 4 (Intermediate)
296
+ def template_damping_classification():
297
+ """
298
+ Free Vibration: Damping Parameters and System Classification
299
+
300
+ Scenario:
301
+ This template introduces viscous damping and requires the calculation of
302
+ key damping parameters. Given a system's physical properties (mass,
303
+ stiffness, damping coefficient), the goal is to determine the damping
304
+ ratio (zeta), classify the system's behavior (underdamped, critically
305
+ damped, or overdamped), and calculate the damped natural frequency
306
+ (omega_d) if the system is underdamped.
307
+
308
+ Core Equations:
309
+ omega_n = sqrt(k / m)
310
+ c_c = 2 * sqrt(k * m)
311
+ zeta = c / c_c
312
+ omega_d = omega_n * sqrt(1 - zeta^2) (for zeta < 1)
313
+
314
+ Returns:
315
+ tuple: A tuple containing:
316
+ - str: A question asking for system classification and key parameters.
317
+ - str: A step-by-step solution.
318
+ """
319
+ # 1. Parameterize the inputs to ensure all cases are generated
320
+ mass = round(random.uniform(2.0, 600.0), 2) # in kg
321
+ stiffness = random.randint(2000, 300000) # in N/m
322
+
323
+ # To ensure a good distribution of outcomes, we first select a damping ratio
324
+ # and then calculate the corresponding damping coefficient 'c'.
325
+
326
+ # Choose a scenario: 1 for underdamped, 2 for critically damped, 3 for overdamped
327
+ scenario_choice = random.randint(1, 3)
328
+
329
+ if scenario_choice == 1: # Underdamped
330
+ zeta = round(random.uniform(0.15, 0.85), 3)
331
+ elif scenario_choice == 2: # Critically Damped
332
+ zeta = 1.0
333
+ else: # Overdamped
334
+ zeta = round(random.uniform(1.2, 2.5), 3)
335
+
336
+ # Standardize precision for final outputs
337
+ precision = 4
338
+
339
+ # 2. Perform the core calculations for the solution
340
+
341
+ # Step A: Calculate the critical damping coefficient (c_c)
342
+ critical_damping_c = 2 * math.sqrt(stiffness * mass)
343
+
344
+ # Step B: Calculate the actual damping coefficient (c) based on the desired zeta
345
+ damping_coefficient_c = zeta * critical_damping_c
346
+
347
+ # Step C: Determine the system classification based on zeta
348
+ if zeta < 1:
349
+ classification = "Underdamped"
350
+ # Also calculate natural and damped frequencies for the solution
351
+ omega_n = math.sqrt(stiffness / mass)
352
+ omega_d = omega_n * math.sqrt(1 - zeta**2)
353
+ elif zeta == 1:
354
+ classification = "Critically Damped"
355
+ else:
356
+ classification = "Overdamped"
357
+
358
+ # 3. Generate the question and solution strings
359
+
360
+ question = (
361
+ f"A damped single-degree-of-freedom system has a mass of {mass} kg, "
362
+ f"a spring stiffness of {stiffness} N/m, and a viscous damping coefficient of "
363
+ f"{round(damping_coefficient_c, 2)} N-s/m. "
364
+ f"Calculate the damping ratio (zeta) and classify the system as underdamped, "
365
+ f"critically damped, or overdamped. If the system is underdamped, also "
366
+ f"calculate its damped natural frequency (omega_d)."
367
+ )
368
+
369
+ solution = (
370
+ f"**Given:**\n"
371
+ f"Mass (m) = {mass} kg\n"
372
+ f"Stiffness (k) = {stiffness} N/m\n"
373
+ f"Damping Coefficient (c) = {round(damping_coefficient_c, 2)} N-s/m\n\n"
374
+
375
+ f"**Step 1:** Calculate the critical damping coefficient (c_c).\n"
376
+ f"The formula is: c_c = 2 * sqrt(k * m)\n"
377
+ f"c_c = 2 * sqrt({stiffness} * {mass})\n"
378
+ f"c_c = {round(critical_damping_c, precision)} N-s/m\n\n"
379
+
380
+ f"**Step 2:** Calculate the damping ratio (zeta).\n"
381
+ f"The formula is: zeta = c / c_c\n"
382
+ f"zeta = {round(damping_coefficient_c, 2)} / {round(critical_damping_c, precision)}\n"
383
+ f"zeta = {round(zeta, precision)}\n\n"
384
+
385
+ f"**Step 3:** Classify the system based on the value of zeta.\n"
386
+ f"Since zeta {'>' if zeta > 1 else '<' if zeta < 1 else '='} 1, the system is **{classification}**.\n\n"
387
+ )
388
+
389
+ # Add the final step only if the system is underdamped
390
+ if classification == "Underdamped":
391
+ solution += (
392
+ f"**Step 4:** Since the system is underdamped, calculate the damped natural frequency (omega_d).\n"
393
+ f"First, find the undamped natural frequency (omega_n):\n"
394
+ f"omega_n = sqrt(k / m) = sqrt({stiffness} / {mass}) = {round(omega_n, precision)} rad/s\n\n"
395
+ f"Now, use the formula: omega_d = omega_n * sqrt(1 - zeta^2)\n"
396
+ f"omega_d = {round(omega_n, precision)} * sqrt(1 - {round(zeta, precision)}^2)\n"
397
+ f"omega_d = {round(omega_d, precision)} rad/s\n\n"
398
+
399
+ f"**Answer:**\n"
400
+ f"The damping ratio is {round(zeta, precision)}. The system is **{classification}**.\n"
401
+ f"The damped natural frequency is {round(omega_d, precision)} rad/s."
402
+ )
403
+ else:
404
+ solution += (
405
+ f"**Answer:**\n"
406
+ f"The damping ratio is {round(zeta, precision)}. The system is **{classification}**."
407
+ )
408
+
409
+ return question, solution
410
+
411
+
412
+ # Template 5 (Intermediate)
413
+ def template_logarithmic_decrement():
414
+ """
415
+ Free Vibration: Logarithmic Decrement and Damping Ratio
416
+
417
+ Scenario:
418
+ This template models a practical method for determining the damping in
419
+ an underdamped system. By observing the amplitude of vibration at two
420
+ distinct points in time, separated by a known number of cycles, one can
421
+ calculate the logarithmic decrement, which directly relates to the system's
422
+ damping ratio.
423
+
424
+ Core Equations:
425
+ delta = (1/n) * ln(x1 / x_{n+1})
426
+ zeta = delta / sqrt((2*pi)^2 + delta^2)
427
+
428
+ Returns:
429
+ tuple: A tuple containing:
430
+ - str: A question asking for the logarithmic decrement and damping ratio.
431
+ - str: A step-by-step solution.
432
+ """
433
+ # 1. Parameterize the inputs with random values
434
+
435
+ # Initialize variables to enter the loop
436
+ final_amplitude_x_n_plus_1 = 0.0
437
+
438
+ # Use a loop to ensure the final rounded amplitude is never zero.
439
+ # This prevents a division-by-zero error in cases of high decay.
440
+ while final_amplitude_x_n_plus_1 <= 0.0:
441
+ # To ensure physically consistent values, we first generate a realistic
442
+ # damping ratio (zeta) and work backward to find the amplitudes.
443
+ zeta_actual = random.uniform(0.05, 0.25)
444
+
445
+ # Calculate the corresponding logarithmic decrement
446
+ delta_actual = (2 * math.pi * zeta_actual) / math.sqrt(1 - zeta_actual**2)
447
+
448
+ # Choose a number of cycles for the measurement
449
+ num_cycles_n = random.randint(2, 8) # Reduced max cycles to lower chance of zeroing out
450
+
451
+ # Set a random initial amplitude in mm
452
+ initial_amplitude_x1 = round(random.uniform(20.0, 100.0), 1)
453
+
454
+ # Calculate the final amplitude based on the decrement and number of cycles
455
+ calculated_final_amplitude = initial_amplitude_x1 / math.exp(num_cycles_n * delta_actual)
456
+
457
+ # Round the final amplitude to make it look like a measured value
458
+ final_amplitude_x_n_plus_1 = round(calculated_final_amplitude, 1)
459
+
460
+ # Standardize precision for final outputs in the solution
461
+ precision = 4
462
+
463
+ # 2. Perform the core calculations for the solution (as the user would)
464
+
465
+ # Step A: Calculate the logarithmic decrement from the given amplitudes
466
+ log_decrement_delta = (1 / num_cycles_n) * math.log(initial_amplitude_x1 / final_amplitude_x_n_plus_1)
467
+
468
+ # Step B: Calculate the damping ratio from the decrement
469
+ damping_ratio_zeta = log_decrement_delta / math.sqrt((2 * math.pi)**2 + log_decrement_delta**2)
470
+
471
+ # 3. Generate the question and solution strings
472
+
473
+ question = (
474
+ f"The amplitude of free vibration of an underdamped system is observed to decay over time. "
475
+ f"The initial amplitude is measured to be {initial_amplitude_x1} mm. After {num_cycles_n} complete cycles, "
476
+ f"the amplitude is measured to be {final_amplitude_x_n_plus_1} mm. "
477
+ f"Based on these observations, calculate the logarithmic decrement (delta) and the damping ratio (zeta) of the system."
478
+ )
479
+
480
+ solution = (
481
+ f"**Given:**\n"
482
+ f"Initial Amplitude (x1) = {initial_amplitude_x1} mm\n"
483
+ f"Amplitude after {num_cycles_n} cycles (x{num_cycles_n + 1}) = {final_amplitude_x_n_plus_1} mm\n"
484
+ f"Number of cycles (n) = {num_cycles_n}\n\n"
485
+
486
+ f"**Step 1:** Calculate the logarithmic decrement (delta).\n"
487
+ f"The formula is: delta = (1/n) * ln(x1 / x_{num_cycles_n + 1})\n"
488
+ f"delta = (1 / {num_cycles_n}) * ln({initial_amplitude_x1} / {final_amplitude_x_n_plus_1})\n"
489
+ f"delta = (1 / {num_cycles_n}) * ln({round(initial_amplitude_x1 / final_amplitude_x_n_plus_1, precision)})\n"
490
+ f"delta = {round(log_decrement_delta, precision)}\n\n"
491
+
492
+ f"**Step 2:** Calculate the damping ratio (zeta) from the logarithmic decrement.\n"
493
+ f"The formula relating zeta and delta is: zeta = delta / sqrt((2*pi)^2 + delta^2)\n"
494
+ f"zeta = {round(log_decrement_delta, precision)} / sqrt((2*pi)^2 + {round(log_decrement_delta, precision)}^2)\n"
495
+ f"zeta = {round(damping_ratio_zeta, precision)}\n\n"
496
+
497
+ f"**Answer:**\n"
498
+ f"The logarithmic decrement is {round(log_decrement_delta, precision)}.\n"
499
+ f"The damping ratio of the system is {round(damping_ratio_zeta, precision)}."
500
+ )
501
+
502
+ return question, solution
503
+
504
+
505
+ # Template 6 (Advanced)
506
+ def template_equivalent_stiffness_frequency():
507
+ """
508
+ Free Vibration: Equivalent Stiffness and Natural Frequency
509
+
510
+ Scenario:
511
+ This template addresses systems with multiple springs. It requires first
512
+ simplifying a spring network (either in series or parallel) into a single
513
+ equivalent spring. After finding the equivalent stiffness (k_eq), the
514
+ natural frequency of the overall system is calculated. This adds a
515
+ critical modeling step to the standard frequency calculation.
516
+
517
+ Core Equations:
518
+ Parallel Stiffness: k_eq = k1 + k2
519
+ Series Stiffness: k_eq = (k1 * k2) / (k1 + k2)
520
+ Natural Frequency: omega_n = sqrt(k_eq / m)
521
+
522
+ Returns:
523
+ tuple: A tuple containing:
524
+ - str: A question asking for the equivalent stiffness and natural frequency.
525
+ - str: A step-by-step solution.
526
+ """
527
+ # 1. Parameterize the inputs with random values
528
+ mass = round(random.uniform(5.0, 100.0), 2) # in kg
529
+ stiffness_1 = random.randint(1000, 50000) # in N/m
530
+ stiffness_2 = random.randint(1000, 50000) # in N/m
531
+
532
+ # Randomly choose the spring configuration
533
+ configuration = random.choice(['series', 'parallel'])
534
+
535
+ # Standardize precision for final outputs
536
+ precision = 3
537
+
538
+ # 2. Perform the core calculations for the solution
539
+
540
+ # Step A: Calculate the equivalent stiffness (k_eq) based on the configuration
541
+ if configuration == 'parallel':
542
+ k_eq = stiffness_1 + stiffness_2
543
+ formula_str = "k_eq = k1 + k2"
544
+ calculation_str = f"k_eq = {stiffness_1} + {stiffness_2} = {k_eq} N/m"
545
+ else: # configuration == 'series'
546
+ k_eq = (stiffness_1 * stiffness_2) / (stiffness_1 + stiffness_2)
547
+ formula_str = "k_eq = (k1 * k2) / (k1 + k2)"
548
+ calculation_str = (
549
+ f"k_eq = ({stiffness_1} * {stiffness_2}) / ({stiffness_1} + {stiffness_2}) = "
550
+ f"{round(k_eq, 2)} N/m"
551
+ )
552
+
553
+ # Step B: Calculate the natural frequency using the equivalent stiffness
554
+ omega_n = math.sqrt(k_eq / mass)
555
+
556
+ # 3. Generate the question and solution strings
557
+
558
+ question = (
559
+ f"A mass of {mass} kg is attached to a system of two springs. The first spring has a "
560
+ f"stiffness (k1) of {stiffness_1} N/m, and the second spring has a stiffness (k2) of {stiffness_2} N/m. "
561
+ f"The two springs are arranged in {configuration}. "
562
+ f"Determine the equivalent stiffness of the spring system and the resulting undamped "
563
+ f"natural frequency (omega_n) in rad/s."
564
+ )
565
+
566
+ solution = (
567
+ f"**Given:**\n"
568
+ f"Mass (m) = {mass} kg\n"
569
+ f"Stiffness 1 (k1) = {stiffness_1} N/m\n"
570
+ f"Stiffness 2 (k2) = {stiffness_2} N/m\n"
571
+ f"Configuration = {configuration.capitalize()}\n\n"
572
+
573
+ f"**Step 1:** Calculate the equivalent stiffness (k_eq) for the {configuration} configuration.\n"
574
+ f"The formula for springs in {configuration} is: {formula_str}\n"
575
+ f"{calculation_str}\n\n"
576
+
577
+ f"**Step 2:** Calculate the undamped natural frequency (omega_n) using the equivalent stiffness.\n"
578
+ f"The formula is: omega_n = sqrt(k_eq / m)\n"
579
+ f"omega_n = sqrt({round(k_eq, 2)} / {mass})\n"
580
+ f"omega_n = {round(omega_n, precision)} rad/s\n\n"
581
+
582
+ f"**Answer:**\n"
583
+ f"The equivalent stiffness of the system is {round(k_eq, 2)} N/m.\n"
584
+ f"The undamped natural frequency is {round(omega_n, precision)} rad/s."
585
+ )
586
+
587
+ return question, solution
588
+
589
+
590
+ def main():
591
+ """
592
+ Generate numerous instances of each free vibration of single-degree-of-freedom systems
593
+ template with different random seeds and write the results to a JSONL file.
594
+ """
595
+ import json
596
+ import os
597
+
598
+ # Define the output path (Modify this path according to where you are running the code from)
599
+ output_file = "testset/mechanical_engineering/vibrations_and_acoustics/single_degree_of_freedom_systems.jsonl"
600
+
601
+ # Create the directory if it doesn't exist
602
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
603
+
604
+ # List of template functions with their ID and level
605
+ templates = [
606
+ (template_undamped_natural_frequency_translational, "undamped_natural_frequency_translational", "Easy"),
607
+ (template_undamped_natural_frequency_torsional, "undamped_natural_frequency_torsional", "Easy"),
608
+ (template_undamped_response_initial_conditions, "undamped_response_initial_conditions", "Intermediate"),
609
+ (template_damping_classification, "damping_classification", "Intermediate"),
610
+ (template_logarithmic_decrement, "logarithmic_decrement", "Intermediate"),
611
+ (template_equivalent_stiffness_frequency, "equivalent_stiffness_frequency", "Advanced"),
612
+ ]
613
+
614
+ # List to store all generated problems
615
+ all_problems = []
616
+
617
+ # Generate problems for each template
618
+ for template_func, id_name, level in templates:
619
+ for _ in range(50):
620
+ # Generate a unique seed for each problem
621
+ seed = random.randint(1_000_000_000, 4_000_000_000)
622
+ random.seed(seed)
623
+
624
+ # Generate the problem and solution
625
+ question, solution = template_func()
626
+
627
+ # Create a JSON entry
628
+ problem_entry = {
629
+ "seed": seed,
630
+ "branch": "mechanical_engineering",
631
+ "domain": "vibrations_and_acoustics",
632
+ "area": "single_degree_of_freedom_systems",
633
+ "id": id_name,
634
+ "level": level,
635
+ "question": question,
636
+ "solution": solution
637
+ }
638
+
639
+ # Add to the list of problems
640
+ all_problems.append(problem_entry)
641
+
642
+ # Shuffle the problems to mix templates and levels
643
+ random.shuffle(all_problems)
644
+
645
+ # Write all problems to a .jsonl file
646
+ with open(output_file, "w") as file:
647
+ for problem in all_problems:
648
+ file.write(json.dumps(problem))
649
+ file.write("\n")
650
+
651
+ print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
652
+
653
+
654
+ if __name__ == "__main__":
655
+ main()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ streamlit>=1.30.0
2
+ pandas
3
+ numpy
4
+ scipy
src/__pycache__/storage.cpython-311.pyc ADDED
Binary file (2.25 kB). View file
 
src/__pycache__/template_loader.cpython-311.pyc ADDED
Binary file (5.16 kB). View file
 
src/storage.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/storage.py
2
+ import json
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+
6
+ def _get_review_dir() -> Path:
7
+ """
8
+ If Hugging Face Persistent Storage is enabled, it is mounted at /data.
9
+ Otherwise fall back to repo-local ./reviews (ephemeral on Spaces).
10
+ """
11
+ persistent_root = Path("/data")
12
+ if persistent_root.exists() and persistent_root.is_dir():
13
+ return persistent_root / "reviews"
14
+ return Path(__file__).parent.parent / "reviews"
15
+
16
+ REVIEW_DIR = _get_review_dir()
17
+ REVIEW_DIR.mkdir(parents=True, exist_ok=True)
18
+
19
+ def save_review(annotator_name, branch, area, template_name, scores, decision, feedback):
20
+ """
21
+ Appends a single review to an annotator-specific JSONL file.
22
+ """
23
+ safe_name = "".join([c for c in annotator_name if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_")
24
+ safe_branch = branch.replace(" ", "_")
25
+
26
+ filename = REVIEW_DIR / f"{safe_name}_{safe_branch}.jsonl"
27
+
28
+ review_data = {
29
+ "timestamp": datetime.now().isoformat(),
30
+ "annotator_id": annotator_name,
31
+ "branch": branch,
32
+ "area": area,
33
+ "template": template_name,
34
+ "scores": {
35
+ "physical_plausibility": scores[0],
36
+ "mathematical_correctness": scores[1],
37
+ "pedagogical_clarity": scores[2],
38
+ },
39
+ "decision": decision,
40
+ "feedback": feedback,
41
+ }
42
+
43
+ with open(filename, "a", encoding="utf-8") as f:
44
+ f.write(json.dumps(review_data) + "\n")
45
+
46
+ return str(filename)
src/template_loader.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ 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()
test_loader.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import traceback
3
+ from src.template_loader import (
4
+ get_branches,
5
+ get_areas,
6
+ get_template_files,
7
+ load_template_functions
8
+ )
9
+
10
+ def main():
11
+ print("--- Starting Template Loader Test (V2) ---")
12
+
13
+ # 1. Test Branches
14
+ branches = get_branches()
15
+ print(f"\nBranches Found: {branches}")
16
+
17
+ if not branches:
18
+ print("No branches found. Check your directory structure.")
19
+ return
20
+
21
+ # 2. Test Areas (using the first branch found, likely 'chemical_engineering')
22
+ test_branch = "chemical_engineering"
23
+ if test_branch not in branches:
24
+ test_branch = branches[0]
25
+
26
+ areas = get_areas(test_branch)
27
+ print(f"Areas in '{test_branch}': {areas}")
28
+
29
+ if not areas:
30
+ print(f"No areas found in {test_branch}.")
31
+ return
32
+
33
+ # 3. Test Files (using 'reaction_kinetics' if available)
34
+ test_area = "reaction_kinetics"
35
+ if test_area not in areas:
36
+ test_area = areas[0]
37
+
38
+ files = get_template_files(test_branch, test_area)
39
+ print(f"Files in '{test_branch}/{test_area}': {files}")
40
+
41
+ # We specifically want to test 'mole_balances' if it exists
42
+ target_file = "mole_balances"
43
+ if target_file not in files:
44
+ print(f"WARNING: 'mole_balances.py' not found. Using first available file: {files[0]}")
45
+ target_file = files[0]
46
+
47
+ # 4. Test Function Extraction (The Logic Upgrade)
48
+ print(f"\nInspecting file: '{target_file}.py'...")
49
+ try:
50
+ # This loads the file and finds all def template_...():
51
+ templates = load_template_functions(test_branch, test_area, target_file)
52
+
53
+ if not templates:
54
+ print("No functions starting with 'template_' found in this file.")
55
+ return
56
+
57
+ print(f"Found {len(templates)} template functions:")
58
+ for name, func in templates:
59
+ print(f" - {name}")
60
+
61
+ # 5. Test Execution (Run the first function found)
62
+ first_func_name, first_func_obj = templates[0]
63
+ print(f"\nExecuting '{first_func_name}'...")
64
+
65
+ question, solution = first_func_obj()
66
+
67
+ print("\n--- [Output Preview] ---")
68
+ print(f"Question: {question[:100]}...") # Print first 100 chars
69
+ print(f"Solution: {solution[:100]}...")
70
+ print("------------------------")
71
+ print("SUCCESS: Template loaded and executed correctly.")
72
+
73
+ except Exception as e:
74
+ print(f"CRITICAL ERROR during loading/execution:")
75
+ traceback.print_exc()
76
+
77
+ if __name__ == "__main__":
78
+ main()