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