Spaces:
Sleeping
Sleeping
Usmansafdarktk commited on
Commit ·
b9bf0fc
0
Parent(s):
Deploy EngChain annotator
Browse files- README.md +9 -0
- app.py +255 -0
- data/templates/branches/chemical_engineering/__pycache__/constants.cpython-311.pyc +0 -0
- data/templates/branches/chemical_engineering/constants.py +629 -0
- 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/conversion_and_reactor_sizing.py +311 -0
- data/templates/branches/chemical_engineering/reaction_kinetics/mole_balances.py +431 -0
- data/templates/branches/chemical_engineering/reaction_kinetics/stoichiometry.py +565 -0
- data/templates/branches/chemical_engineering/thermodynamics/heat_effects.py +476 -0
- data/templates/branches/chemical_engineering/thermodynamics/volumetric_properties_pure_fluids.py +701 -0
- data/templates/branches/chemical_engineering/transport_phenomena/shell_momentum_balances.py +339 -0
- data/templates/branches/chemical_engineering/transport_phenomena/viscosity_and_momentum_transport.py +514 -0
- data/templates/branches/electrical_engineering/__pycache__/constants.cpython-311.pyc +0 -0
- data/templates/branches/electrical_engineering/constants.py +67 -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/deterministic_and_random_signal_analysis.py +570 -0
- data/templates/branches/electrical_engineering/digital_communications/digital_modulation_schemes.py +664 -0
- 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/electromagnetics_and_waves/electrostatics.py +566 -0
- data/templates/branches/electrical_engineering/electromagnetics_and_waves/magnetostatics.py +168 -0
- data/templates/branches/electrical_engineering/electromagnetics_and_waves/waves_and_phasors.py +650 -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/continuous_time_signals.py +621 -0
- data/templates/branches/electrical_engineering/signals_and_systems/discrete_time_signals.py +817 -0
- data/templates/branches/mechanical_engineering/constants.py +304 -0
- data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_kinematics.py +734 -0
- data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_statics.py +616 -0
- data/templates/branches/mechanical_engineering/mechanics_of_materials/stress_and_strain.py +916 -0
- data/templates/branches/mechanical_engineering/mechanics_of_materials/torsion.py +604 -0
- data/templates/branches/mechanical_engineering/vibrations_and_acoustics/harmonically_excited_vibrations.py +584 -0
- data/templates/branches/mechanical_engineering/vibrations_and_acoustics/single_degree_of_freedom_systems.py +655 -0
- requirements.txt +4 -0
- src/__pycache__/storage.cpython-311.pyc +0 -0
- src/__pycache__/template_loader.cpython-311.pyc +0 -0
- src/storage.py +46 -0
- src/template_loader.py +69 -0
- test_loader.py +78 -0
README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: EngChain Annotator
|
| 3 |
+
emoji: 🛡️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
python_version: "3.11"
|
| 8 |
+
sdk_version: "1.25.0"
|
| 9 |
+
---
|
app.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import traceback
|
| 3 |
+
import random
|
| 4 |
+
from src.template_loader import (
|
| 5 |
+
get_branches,
|
| 6 |
+
get_areas,
|
| 7 |
+
get_template_files,
|
| 8 |
+
load_template_functions,
|
| 9 |
+
get_source_code
|
| 10 |
+
)
|
| 11 |
+
from src.storage import save_review
|
| 12 |
+
|
| 13 |
+
# --- Page Configuration ---
|
| 14 |
+
st.set_page_config(layout="wide", page_title="EngChain Annotator")
|
| 15 |
+
|
| 16 |
+
# --- Custom CSS for Styling ---
|
| 17 |
+
# This forces the primary buttons to have a specific look (optional but helps visibility)
|
| 18 |
+
st.markdown("""
|
| 19 |
+
<style>
|
| 20 |
+
div.stButton > button:first-child {
|
| 21 |
+
font-weight: bold;
|
| 22 |
+
}
|
| 23 |
+
</style>
|
| 24 |
+
""", unsafe_allow_html=True)
|
| 25 |
+
|
| 26 |
+
# --- Helper Function: Build the Queue ---
|
| 27 |
+
def build_review_queue(branch):
|
| 28 |
+
"""
|
| 29 |
+
Scans the entire branch and creates a list of all templates to review.
|
| 30 |
+
Returns: List of dicts {'branch', 'area', 'file', 'name', 'func'}
|
| 31 |
+
"""
|
| 32 |
+
queue = []
|
| 33 |
+
areas = get_areas(branch)
|
| 34 |
+
|
| 35 |
+
progress_bar = st.progress(0)
|
| 36 |
+
status_text = st.empty()
|
| 37 |
+
|
| 38 |
+
total_steps = len(areas)
|
| 39 |
+
for i, area in enumerate(areas):
|
| 40 |
+
status_text.text(f"Loading area: {area}...")
|
| 41 |
+
files = get_template_files(branch, area)
|
| 42 |
+
for file in files:
|
| 43 |
+
# Load all functions from this file
|
| 44 |
+
try:
|
| 45 |
+
funcs = load_template_functions(branch, area, file)
|
| 46 |
+
for func_name, func_obj in funcs:
|
| 47 |
+
queue.append({
|
| 48 |
+
"branch": branch,
|
| 49 |
+
"area": area,
|
| 50 |
+
"file": file,
|
| 51 |
+
"name": func_name,
|
| 52 |
+
"func": func_obj
|
| 53 |
+
})
|
| 54 |
+
except Exception as e:
|
| 55 |
+
print(f"Error loading {file}: {e}")
|
| 56 |
+
progress_bar.progress((i + 1) / total_steps)
|
| 57 |
+
|
| 58 |
+
status_text.empty()
|
| 59 |
+
progress_bar.empty()
|
| 60 |
+
return queue
|
| 61 |
+
|
| 62 |
+
# --- Session State Initialization ---
|
| 63 |
+
if "app_mode" not in st.session_state:
|
| 64 |
+
st.session_state["app_mode"] = "landing" # landing, review, done
|
| 65 |
+
if "review_queue" not in st.session_state:
|
| 66 |
+
st.session_state["review_queue"] = []
|
| 67 |
+
if "current_index" not in st.session_state:
|
| 68 |
+
st.session_state["current_index"] = 0
|
| 69 |
+
if "annotator_name" not in st.session_state:
|
| 70 |
+
st.session_state["annotator_name"] = ""
|
| 71 |
+
if "current_branch" not in st.session_state:
|
| 72 |
+
st.session_state["current_branch"] = ""
|
| 73 |
+
if "review_submitted" not in st.session_state:
|
| 74 |
+
st.session_state["review_submitted"] = False
|
| 75 |
+
|
| 76 |
+
# ==========================================
|
| 77 |
+
# VIEW 1: LANDING PAGE
|
| 78 |
+
# ==========================================
|
| 79 |
+
if st.session_state["app_mode"] == "landing":
|
| 80 |
+
st.title("🛡️ EngChain Verification Portal")
|
| 81 |
+
|
| 82 |
+
st.markdown("""
|
| 83 |
+
### Welcome, Expert Annotator!
|
| 84 |
+
Thank you for contributing to **EngChain**. Your task is to verify the correctness and quality
|
| 85 |
+
of our symbolic engineering templates.
|
| 86 |
+
|
| 87 |
+
**Instructions:**
|
| 88 |
+
1. Select your Engineering Domain.
|
| 89 |
+
2. Enter your Name/ID.
|
| 90 |
+
3. You will be guided through templates one-by-one.
|
| 91 |
+
4. For each template, check the **Code** and the **Generated Trace**.
|
| 92 |
+
5. Rate it, Approve/Reject it, and click **Submit**.
|
| 93 |
+
""")
|
| 94 |
+
|
| 95 |
+
st.markdown("---")
|
| 96 |
+
|
| 97 |
+
with st.form("onboarding_form"):
|
| 98 |
+
st.subheader("Annotator Details")
|
| 99 |
+
col1, col2 = st.columns(2)
|
| 100 |
+
|
| 101 |
+
with col1:
|
| 102 |
+
branches = get_branches()
|
| 103 |
+
branch_input = st.selectbox("Select Your Domain", branches)
|
| 104 |
+
|
| 105 |
+
with col2:
|
| 106 |
+
name_input = st.text_input("Enter Your Name / ID")
|
| 107 |
+
|
| 108 |
+
# Button styling: type="primary" gives it emphasis (color depends on theme, usually red/blue)
|
| 109 |
+
submitted = st.form_submit_button("Start Annotation Session", type="primary")
|
| 110 |
+
|
| 111 |
+
if submitted:
|
| 112 |
+
if not name_input.strip():
|
| 113 |
+
st.error("Please enter your name to proceed.")
|
| 114 |
+
else:
|
| 115 |
+
st.session_state["annotator_name"] = name_input
|
| 116 |
+
st.session_state["current_branch"] = branch_input
|
| 117 |
+
|
| 118 |
+
# Build the queue
|
| 119 |
+
with st.spinner(f"Gathering templates for {branch_input}..."):
|
| 120 |
+
queue = build_review_queue(branch_input)
|
| 121 |
+
|
| 122 |
+
st.session_state["review_queue"] = queue
|
| 123 |
+
st.session_state["current_index"] = 0
|
| 124 |
+
st.session_state["app_mode"] = "review"
|
| 125 |
+
st.rerun()
|
| 126 |
+
|
| 127 |
+
# ==========================================
|
| 128 |
+
# VIEW 2: REVIEW WORKSPACE
|
| 129 |
+
# ==========================================
|
| 130 |
+
elif st.session_state["app_mode"] == "review":
|
| 131 |
+
|
| 132 |
+
queue = st.session_state["review_queue"]
|
| 133 |
+
idx = st.session_state["current_index"]
|
| 134 |
+
|
| 135 |
+
# Check if we are done
|
| 136 |
+
if idx >= len(queue):
|
| 137 |
+
st.session_state["app_mode"] = "done"
|
| 138 |
+
st.rerun()
|
| 139 |
+
|
| 140 |
+
current_item = queue[idx]
|
| 141 |
+
|
| 142 |
+
# --- Sidebar Info ---
|
| 143 |
+
st.sidebar.title("Progress")
|
| 144 |
+
st.sidebar.progress((idx) / len(queue))
|
| 145 |
+
st.sidebar.write(f"Template: {idx + 1} / {len(queue)}")
|
| 146 |
+
|
| 147 |
+
st.sidebar.markdown("---")
|
| 148 |
+
st.sidebar.subheader("Current Context")
|
| 149 |
+
|
| 150 |
+
# Distinct styling for keys and values
|
| 151 |
+
st.sidebar.markdown("**📂 Area:**")
|
| 152 |
+
st.sidebar.info(current_item['area'])
|
| 153 |
+
|
| 154 |
+
st.sidebar.markdown("**📄 File:**")
|
| 155 |
+
st.sidebar.info(current_item['file'])
|
| 156 |
+
|
| 157 |
+
st.sidebar.markdown("**🧩 Function:**")
|
| 158 |
+
st.sidebar.info(current_item['name'])
|
| 159 |
+
|
| 160 |
+
# --- Main Content ---
|
| 161 |
+
st.title(f"Reviewing: {current_item['name']}")
|
| 162 |
+
|
| 163 |
+
# Tabs for Inspection
|
| 164 |
+
tab1, tab2 = st.tabs(["Source Code", "Generated Trace"])
|
| 165 |
+
|
| 166 |
+
with tab1:
|
| 167 |
+
code_text = get_source_code(current_item['branch'], current_item['area'], current_item['file'])
|
| 168 |
+
st.code(code_text, language="python", line_numbers=True)
|
| 169 |
+
|
| 170 |
+
with tab2:
|
| 171 |
+
col_gen, _ = st.columns([1, 4])
|
| 172 |
+
# Renamed button for clarity
|
| 173 |
+
if col_gen.button("Generate New Random Instance"):
|
| 174 |
+
pass # Rerun trigger to get new random numbers
|
| 175 |
+
|
| 176 |
+
try:
|
| 177 |
+
question, solution = current_item['func']()
|
| 178 |
+
st.markdown("#### Question")
|
| 179 |
+
st.info(question)
|
| 180 |
+
st.markdown("#### Solution Trace")
|
| 181 |
+
st.success(solution)
|
| 182 |
+
except Exception as e:
|
| 183 |
+
st.error("Template Execution Failed")
|
| 184 |
+
st.code(traceback.format_exc())
|
| 185 |
+
|
| 186 |
+
st.markdown("---")
|
| 187 |
+
|
| 188 |
+
# --- Scoring Form ---
|
| 189 |
+
st.subheader("Audit Decision")
|
| 190 |
+
|
| 191 |
+
# Use a container so we can disable the form after submission
|
| 192 |
+
with st.container():
|
| 193 |
+
# Check if already submitted for this specific template
|
| 194 |
+
is_submitted = st.session_state.get("review_submitted", False)
|
| 195 |
+
|
| 196 |
+
if not is_submitted:
|
| 197 |
+
# FORM STATE
|
| 198 |
+
with st.form("audit_form"):
|
| 199 |
+
c1, c2, c3 = st.columns(3)
|
| 200 |
+
phys = c1.slider("Physical Plausibility", 1, 5, 5)
|
| 201 |
+
math = c2.slider("Mathematical Correctness", 1, 5, 5)
|
| 202 |
+
ped = c3.slider("Pedagogical Clarity", 1, 5, 5)
|
| 203 |
+
|
| 204 |
+
decision = st.radio("Certification:", ["Approve", "Reject"], horizontal=True)
|
| 205 |
+
feedback = st.text_area("Feedback (Required for Rejection)", placeholder="Explain any errors found...")
|
| 206 |
+
|
| 207 |
+
# Removed emoji from button
|
| 208 |
+
submit_review = st.form_submit_button("Submit Review")
|
| 209 |
+
|
| 210 |
+
if submit_review:
|
| 211 |
+
if decision == "Reject" and not feedback.strip():
|
| 212 |
+
st.error("Feedback is required for Rejection.")
|
| 213 |
+
else:
|
| 214 |
+
# Save to disk
|
| 215 |
+
save_review(
|
| 216 |
+
st.session_state["annotator_name"],
|
| 217 |
+
current_item["branch"],
|
| 218 |
+
current_item["area"],
|
| 219 |
+
current_item["name"],
|
| 220 |
+
[phys, math, ped],
|
| 221 |
+
decision,
|
| 222 |
+
feedback
|
| 223 |
+
)
|
| 224 |
+
st.success("Review Saved!")
|
| 225 |
+
st.session_state["review_submitted"] = True
|
| 226 |
+
st.rerun()
|
| 227 |
+
else:
|
| 228 |
+
# POST-SUBMISSION STATE (Show Next Button)
|
| 229 |
+
st.success("Review recorded for this template.")
|
| 230 |
+
|
| 231 |
+
# Removed emoji, kept primary type for visibility
|
| 232 |
+
if st.button("Proceed to Next Template", type="primary"):
|
| 233 |
+
st.session_state["current_index"] += 1
|
| 234 |
+
st.session_state["review_submitted"] = False
|
| 235 |
+
st.rerun()
|
| 236 |
+
|
| 237 |
+
# ==========================================
|
| 238 |
+
# VIEW 3: COMPLETION PAGE
|
| 239 |
+
# ==========================================
|
| 240 |
+
elif st.session_state["app_mode"] == "done":
|
| 241 |
+
# Removed balloons()
|
| 242 |
+
st.title("Session Complete")
|
| 243 |
+
|
| 244 |
+
st.success(f"""
|
| 245 |
+
**Thank you, {st.session_state['annotator_name']}!**
|
| 246 |
+
|
| 247 |
+
You have successfully reviewed all **{len(st.session_state['review_queue'])}** templates
|
| 248 |
+
in the **{st.session_state['current_branch']}** domain.
|
| 249 |
+
""")
|
| 250 |
+
|
| 251 |
+
st.info("Your reviews have been saved securely. You may now close this tab.")
|
| 252 |
+
|
| 253 |
+
if st.button("Start New Session"):
|
| 254 |
+
st.session_state.clear()
|
| 255 |
+
st.rerun()
|
data/templates/branches/chemical_engineering/__pycache__/constants.cpython-311.pyc
ADDED
|
Binary file (18.7 kB). View file
|
|
|
data/templates/branches/chemical_engineering/constants.py
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Standard: phases referenced at ~25 °C and 1 atm
|
| 2 |
+
|
| 3 |
+
LIQUID_PHASE_REACTANTS = [
|
| 4 |
+
"Ethyl Acetate",
|
| 5 |
+
"Propylene Glycol",
|
| 6 |
+
"Benzene",
|
| 7 |
+
"Toluene",
|
| 8 |
+
"Acetone",
|
| 9 |
+
"Methanol",
|
| 10 |
+
"Ethanol",
|
| 11 |
+
"Isopropanol", # specify "isopropanol (2-propanol)" if needed
|
| 12 |
+
"n-Butanol", # specify isomer as needed
|
| 13 |
+
"Methyl Ethyl Ketone (MEK) / 2-butanone",
|
| 14 |
+
# Formaldehyde is typically a gas at 25°C; aqueous solution is "Formalin (37% aqueous)"
|
| 15 |
+
"Acetic Acid",
|
| 16 |
+
# Phenol is a solid at 25°C (mp ~40.5°C). Include only if working above that temperature.
|
| 17 |
+
"Glycerol",
|
| 18 |
+
"Xylene",
|
| 19 |
+
"Styrene",
|
| 20 |
+
"Aniline",
|
| 21 |
+
"Cyclohexane",
|
| 22 |
+
"Formic Acid",
|
| 23 |
+
"Nitric Acid",
|
| 24 |
+
"Sulfuric Acid",
|
| 25 |
+
"Ethylene Glycol",
|
| 26 |
+
"Diethyl Ether",
|
| 27 |
+
"Tetrahydrofuran (THF)",
|
| 28 |
+
"Chloroform",
|
| 29 |
+
"Acrylonitrile",
|
| 30 |
+
"Dimethylformamide (DMF)",
|
| 31 |
+
"Methyl Methacrylate",
|
| 32 |
+
"Acetic anhydride" # corrected name (a.k.a. ethanoic anhydride)
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
GAS_PHASE_REACTANTS = [
|
| 36 |
+
"Methane",
|
| 37 |
+
"Ethane",
|
| 38 |
+
"Propane",
|
| 39 |
+
"Ammonia",
|
| 40 |
+
"Ethylene",
|
| 41 |
+
"Sulfur Dioxide",
|
| 42 |
+
"Hydrogen Sulfide",
|
| 43 |
+
"Vinyl Chloride",
|
| 44 |
+
"Butadiene",
|
| 45 |
+
"Hydrogen",
|
| 46 |
+
"Oxygen",
|
| 47 |
+
"Nitrogen",
|
| 48 |
+
"Chlorine",
|
| 49 |
+
"Carbon Monoxide",
|
| 50 |
+
"Carbon Dioxide",
|
| 51 |
+
"Propylene",
|
| 52 |
+
"Butane",
|
| 53 |
+
"Acetylene",
|
| 54 |
+
"Nitric Oxide (NO)",
|
| 55 |
+
"Nitrogen Dioxide (NO2)",
|
| 56 |
+
"Hydrogen Chloride (HCl)", # gas at 1 atm
|
| 57 |
+
"Phosgene",
|
| 58 |
+
"Ethylene Oxide",
|
| 59 |
+
"Isobutane",
|
| 60 |
+
"Formaldehyde", # moved here: gas at 25°C
|
| 61 |
+
"Acetaldehyde" # volatile; bp ~20.2°C → effectively gas at 25°C
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
BIOCHEMICAL_SUBSTRATES = [
|
| 65 |
+
"Glucose",
|
| 66 |
+
"Sucrose",
|
| 67 |
+
"Lactose",
|
| 68 |
+
"Fructose",
|
| 69 |
+
"Pyruvate",
|
| 70 |
+
"Maltose",
|
| 71 |
+
"Galactose",
|
| 72 |
+
"Starch", # polymeric solid
|
| 73 |
+
"Cellulose", # polymeric solid
|
| 74 |
+
"Xylose",
|
| 75 |
+
"Glutamate", # often used as salts (e.g., sodium glutamate)
|
| 76 |
+
"Alanine",
|
| 77 |
+
"Lactate", # usually as salt
|
| 78 |
+
"Citrate",
|
| 79 |
+
"Acetyl-CoA", # coenzyme (large, charged)
|
| 80 |
+
"Palmitic Acid",
|
| 81 |
+
"Oleic Acid",
|
| 82 |
+
"Triglycerides",
|
| 83 |
+
"Urea",
|
| 84 |
+
"Aspartate"
|
| 85 |
+
]
|
| 86 |
+
|
| 87 |
+
GENERAL_REACTANTS = LIQUID_PHASE_REACTANTS + GAS_PHASE_REACTANTS + BIOCHEMICAL_SUBSTRATES
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
PRODUCTS = [
|
| 91 |
+
"Product Alpha",
|
| 92 |
+
"Product Beta",
|
| 93 |
+
"Product Gamma",
|
| 94 |
+
"Product Delta",
|
| 95 |
+
"Product Sigma",
|
| 96 |
+
"Product Omega",
|
| 97 |
+
"Product Theta",
|
| 98 |
+
"Product Lambda",
|
| 99 |
+
"Product Zeta",
|
| 100 |
+
"Product Kappa",
|
| 101 |
+
|
| 102 |
+
"Compound P",
|
| 103 |
+
"Compound Q",
|
| 104 |
+
"Compound R",
|
| 105 |
+
"Compound S",
|
| 106 |
+
"Compound T",
|
| 107 |
+
"Compound V",
|
| 108 |
+
"Compound W",
|
| 109 |
+
"Compound X",
|
| 110 |
+
"Compound Y",
|
| 111 |
+
"Compound Z",
|
| 112 |
+
|
| 113 |
+
"Species I",
|
| 114 |
+
"Species II",
|
| 115 |
+
"Species III",
|
| 116 |
+
"Species IV",
|
| 117 |
+
"Species V",
|
| 118 |
+
"Species VI",
|
| 119 |
+
"Species VII",
|
| 120 |
+
"Species VIII",
|
| 121 |
+
|
| 122 |
+
"Material A",
|
| 123 |
+
"Material B",
|
| 124 |
+
"Material C",
|
| 125 |
+
"Material D",
|
| 126 |
+
"Material E",
|
| 127 |
+
"Material F",
|
| 128 |
+
|
| 129 |
+
"Substance One",
|
| 130 |
+
"Substance Two",
|
| 131 |
+
"Substance Three",
|
| 132 |
+
"Substance Four",
|
| 133 |
+
"Substance Five",
|
| 134 |
+
"Substance Six",
|
| 135 |
+
|
| 136 |
+
"Molecule M",
|
| 137 |
+
"Molecule N",
|
| 138 |
+
"Molecule O",
|
| 139 |
+
"Molecule P",
|
| 140 |
+
"Molecule Q",
|
| 141 |
+
"Molecule R",
|
| 142 |
+
|
| 143 |
+
"Entity 1",
|
| 144 |
+
"Entity 2",
|
| 145 |
+
"Entity 3",
|
| 146 |
+
"Entity 4",
|
| 147 |
+
"Entity 5",
|
| 148 |
+
"Entity 6"
|
| 149 |
+
]
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
# A list of common substances used in thermodynamics problems involving phase change.
|
| 153 |
+
THERMO_SUBSTANCES = [
|
| 154 |
+
# Classic working fluids
|
| 155 |
+
"Water",
|
| 156 |
+
"Ammonia",
|
| 157 |
+
"Carbon Dioxide",
|
| 158 |
+
"Sulfur Dioxide",
|
| 159 |
+
|
| 160 |
+
# Hydrocarbons (fuels and refrigerants)
|
| 161 |
+
"Methane",
|
| 162 |
+
"Ethane",
|
| 163 |
+
"Propane",
|
| 164 |
+
"Butane",
|
| 165 |
+
"Isobutane",
|
| 166 |
+
"Pentane",
|
| 167 |
+
"Iso-pentane",
|
| 168 |
+
|
| 169 |
+
# Refrigerants (with ASHRAE designations)
|
| 170 |
+
"Refrigerant-11 (R-11, Trichlorofluoromethane)",
|
| 171 |
+
"Refrigerant-12 (R-12, Dichlorodifluoromethane)",
|
| 172 |
+
"Refrigerant-22 (R-22, Chlorodifluoromethane)",
|
| 173 |
+
"Refrigerant-134a (R-134a, 1,1,1,2-Tetrafluoroethane)",
|
| 174 |
+
"Refrigerant-123 (R-123, Dichlorotrifluoroethane)",
|
| 175 |
+
"Refrigerant-410A (R-410A, blend of difluoromethane and pentafluoroethane)",
|
| 176 |
+
|
| 177 |
+
# Common industrial/organic fluids used in Rankine/Organic Rankine cycles
|
| 178 |
+
"Toluene",
|
| 179 |
+
"Benzene",
|
| 180 |
+
"Ethanol",
|
| 181 |
+
"Methanol",
|
| 182 |
+
"Acetone",
|
| 183 |
+
"n-Hexane",
|
| 184 |
+
"n-Octane",
|
| 185 |
+
"Cyclohexane"
|
| 186 |
+
]
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
# A dictionary with comprehensive critical properties for various substances.
|
| 190 |
+
# Tc: Kelvin (K), Pc: bar, Vc: cm³/mol, Zc: dimensionless, omega: dimensionless.
|
| 191 |
+
CRITICAL_PROPERTIES = {
|
| 192 |
+
"Methane": {"Tc": 190.6, "Pc": 45.99, "Vc": 99.0, "Zc": 0.286, "omega": 0.012},
|
| 193 |
+
"Ethane": {"Tc": 305.3, "Pc": 48.72, "Vc": 146.0, "Zc": 0.279, "omega": 0.100},
|
| 194 |
+
"Propane": {"Tc": 369.8, "Pc": 42.48, "Vc": 200.0, "Zc": 0.276, "omega": 0.152},
|
| 195 |
+
"n-Butane": {"Tc": 425.1, "Pc": 37.96, "Vc": 255.0, "Zc": 0.274, "omega": 0.200},
|
| 196 |
+
"n-Pentane": {"Tc": 469.7, "Pc": 33.7, "Vc": 311.0, "Zc": 0.269, "omega": 0.251},
|
| 197 |
+
"n-Hexane": {"Tc": 507.6, "Pc": 30.12, "Vc": 368.0, "Zc": 0.264, "omega": 0.301},
|
| 198 |
+
"n-Heptane": {"Tc": 540.2, "Pc": 27.36, "Vc": 426.0, "Zc": 0.263, "omega": 0.350},
|
| 199 |
+
"n-Octane": {"Tc": 568.8, "Pc": 24.86, "Vc": 492.0, "Zc": 0.259, "omega": 0.400},
|
| 200 |
+
"Ethylene": {"Tc": 282.4, "Pc": 50.42, "Vc": 131.0, "Zc": 0.281, "omega": 0.087},
|
| 201 |
+
"Propylene": {"Tc": 365.0, "Pc": 46.0, "Vc": 181.0, "Zc": 0.275, "omega": 0.140},
|
| 202 |
+
"Benzene": {"Tc": 562.2, "Pc": 48.95, "Vc": 259.0, "Zc": 0.271, "omega": 0.210},
|
| 203 |
+
"Toluene": {"Tc": 591.8, "Pc": 41.09, "Vc": 316.0, "Zc": 0.264, "omega": 0.263},
|
| 204 |
+
"p-Xylene": {"Tc": 616.2, "Pc": 35.12, "Vc": 379.0, "Zc": 0.260, "omega": 0.321},
|
| 205 |
+
"Methanol": {"Tc": 512.6, "Pc": 80.84, "Vc": 118.0, "Zc": 0.224, "omega": 0.564},
|
| 206 |
+
"Ethanol": {"Tc": 513.9, "Pc": 61.37, "Vc": 167.0, "Zc": 0.248, "omega": 0.645},
|
| 207 |
+
"Acetone": {"Tc": 508.2, "Pc": 46.99, "Vc": 209.0, "Zc": 0.232, "omega": 0.304},
|
| 208 |
+
"Water": {"Tc": 647.1, "Pc": 220.64, "Vc": 55.9, "Zc": 0.229, "omega": 0.345},
|
| 209 |
+
"Ammonia": {"Tc": 405.5, "Pc": 113.53, "Vc": 72.5, "Zc": 0.242, "omega": 0.250},
|
| 210 |
+
"Carbon dioxide": {"Tc": 304.2, "Pc": 73.83, "Vc": 94.0, "Zc": 0.274, "omega": 0.224},
|
| 211 |
+
"Carbon monoxide": {"Tc": 132.9, "Pc": 34.99, "Vc": 93.1, "Zc": 0.295, "omega": 0.045},
|
| 212 |
+
"Oxygen": {"Tc": 154.6, "Pc": 50.43, "Vc": 73.4, "Zc": 0.288, "omega": 0.022},
|
| 213 |
+
"Nitrogen": {"Tc": 126.2, "Pc": 33.98, "Vc": 89.8, "Zc": 0.290, "omega": 0.039},
|
| 214 |
+
"Hydrogen": {"Tc": 33.2, "Pc": 12.97, "Vc": 65.0, "Zc": 0.305, "omega": -0.216},
|
| 215 |
+
"Helium": {"Tc": 5.2, "Pc": 2.27, "Vc": 57.8, "Zc": 0.301, "omega": -0.365},
|
| 216 |
+
"Chlorine": {"Tc": 417.0, "Pc": 77.02, "Vc": 124.0, "Zc": 0.275, "omega": 0.090},
|
| 217 |
+
"Sulfur dioxide": {"Tc": 430.8, "Pc": 78.84, "Vc": 122.0, "Zc": 0.269, "omega": 0.245},
|
| 218 |
+
"Hydrogen sulfide": {"Tc": 373.5, "Pc": 89.63, "Vc": 98.6, "Zc": 0.284, "omega": 0.091}
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
# Common materials which undergo heating with their specific heat capacities in J/g·K
|
| 223 |
+
SUBSTANCES_FOR_HEATING = [
|
| 224 |
+
# Metals & Solids
|
| 225 |
+
{"name": "Iron", "state": "solid", "Cp": 0.449},
|
| 226 |
+
{"name": "Copper", "state": "solid", "Cp": 0.385},
|
| 227 |
+
{"name": "Aluminum", "state": "solid", "Cp": 0.897},
|
| 228 |
+
{"name": "Gold", "state": "solid", "Cp": 0.129},
|
| 229 |
+
{"name": "Lead", "state": "solid", "Cp": 0.16},
|
| 230 |
+
{"name": "Silver", "state": "solid", "Cp": 0.235},
|
| 231 |
+
{"name": "Tungsten", "state": "solid", "Cp": 0.134},
|
| 232 |
+
{"name": "Silicon", "state": "solid", "Cp": 0.705},
|
| 233 |
+
{"name": "Graphite (Carbon)", "state": "solid", "Cp": 0.709},
|
| 234 |
+
{"name": "Glass (typical)", "state": "solid", "Cp": 0.84},
|
| 235 |
+
{"name": "Ice (at 0°C)", "state": "solid", "Cp": 2.09},
|
| 236 |
+
{"name": "Concrete", "state": "solid", "Cp": 0.88},
|
| 237 |
+
{"name": "Wood (typical)", "state": "solid", "Cp": 1.7},
|
| 238 |
+
{"name": "Polyethylene (plastic)", "state": "solid", "Cp": 2.3},
|
| 239 |
+
|
| 240 |
+
# Liquids
|
| 241 |
+
{"name": "Water", "state": "liquid", "Cp": 4.18},
|
| 242 |
+
{"name": "Ethanol", "state": "liquid", "Cp": 2.44},
|
| 243 |
+
{"name": "Methanol", "state": "liquid", "Cp": 2.53},
|
| 244 |
+
{"name": "Acetone", "state": "liquid", "Cp": 2.17},
|
| 245 |
+
{"name": "Mercury", "state": "liquid", "Cp": 0.14},
|
| 246 |
+
{"name": "Glycerol", "state": "liquid", "Cp": 2.43},
|
| 247 |
+
{"name": "Ethylene Glycol (Antifreeze)", "state": "liquid", "Cp": 2.36},
|
| 248 |
+
{"name": "Olive Oil", "state": "liquid", "Cp": 1.97},
|
| 249 |
+
{"name": "Engine Oil (typical)", "state": "liquid", "Cp": 1.9},
|
| 250 |
+
{"name": "Sulfuric Acid", "state": "liquid", "Cp": 1.42},
|
| 251 |
+
|
| 252 |
+
# Gases (at constant pressure, 25°C)
|
| 253 |
+
{"name": "Air (dry)", "state": "gas", "Cp": 1.005},
|
| 254 |
+
{"name": "Nitrogen", "state": "gas", "Cp": 1.04},
|
| 255 |
+
{"name": "Oxygen", "state": "gas", "Cp": 0.918},
|
| 256 |
+
{"name": "Hydrogen", "state": "gas", "Cp": 14.31},
|
| 257 |
+
{"name": "Helium", "state": "gas", "Cp": 5.193},
|
| 258 |
+
{"name": "Argon", "state": "gas", "Cp": 0.520},
|
| 259 |
+
{"name": "Carbon Dioxide", "state": "gas", "Cp": 0.839},
|
| 260 |
+
{"name": "Methane", "state": "gas", "Cp": 2.22},
|
| 261 |
+
{"name": "Ammonia", "state": "gas", "Cp": 2.06},
|
| 262 |
+
{"name": "Water Vapor (Steam, 100°C)", "state": "gas", "Cp": 2.01},
|
| 263 |
+
{"name": "Chlorine", "state": "gas", "Cp": 0.48}
|
| 264 |
+
]
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
# A list of common substances with their molar heats of vaporization (delta_H_vap)
|
| 268 |
+
# at their normal boiling points. All values are in kJ/mol.
|
| 269 |
+
SUBSTANCES_FOR_VAPORIZATION = [
|
| 270 |
+
# Alcohols & Water
|
| 271 |
+
{"name": "Water", "delta_H_vap": 40.66},
|
| 272 |
+
{"name": "Methanol", "delta_H_vap": 35.3},
|
| 273 |
+
{"name": "Ethanol", "delta_H_vap": 38.6},
|
| 274 |
+
{"name": "Isopropanol", "delta_H_vap": 39.85},
|
| 275 |
+
|
| 276 |
+
# Alkanes
|
| 277 |
+
{"name": "Propane", "delta_H_vap": 19.04},
|
| 278 |
+
{"name": "n-Butane", "delta_H_vap": 22.44},
|
| 279 |
+
{"name": "n-Hexane", "delta_H_vap": 28.85},
|
| 280 |
+
|
| 281 |
+
# Organic Solvents
|
| 282 |
+
{"name": "Acetone", "delta_H_vap": 29.1},
|
| 283 |
+
{"name": "Benzene", "delta_H_vap": 30.8},
|
| 284 |
+
{"name": "Toluene", "delta_H_vap": 33.48},
|
| 285 |
+
{"name": "Carbon Tetrachloride", "delta_H_vap": 29.82},
|
| 286 |
+
|
| 287 |
+
# Inorganic & Elemental Substances
|
| 288 |
+
{"name": "Ammonia", "delta_H_vap": 23.3},
|
| 289 |
+
{"name": "Mercury", "delta_H_vap": 59.11},
|
| 290 |
+
{"name": "Nitrogen", "delta_H_vap": 5.57},
|
| 291 |
+
{"name": "Oxygen", "delta_H_vap": 6.82},
|
| 292 |
+
{"name": "Argon", "delta_H_vap": 6.43},
|
| 293 |
+
]
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
# Database of standard heats of formation (ΔH_f°) at 298.15 K in kJ/mol.
|
| 297 |
+
# A value of 0 indicates an element in its standard state.
|
| 298 |
+
HEATS_OF_FORMATION = {
|
| 299 |
+
# Hydrocarbons
|
| 300 |
+
"CH4(g)": -74.8, # Methane
|
| 301 |
+
"C2H6(g)": -84.7, # Ethane
|
| 302 |
+
"C3H8(g)": -103.8, # Propane
|
| 303 |
+
"C6H6(l)": 49.0, # Benzene
|
| 304 |
+
# Alcohols
|
| 305 |
+
"CH3OH(l)": -238.6, # Methanol
|
| 306 |
+
"C2H5OH(l)": -277.7, # Ethanol
|
| 307 |
+
# Common Gases & Products
|
| 308 |
+
"O2(g)": 0,
|
| 309 |
+
"H2(g)": 0,
|
| 310 |
+
"N2(g)": 0,
|
| 311 |
+
"CO(g)": -110.5,
|
| 312 |
+
"CO2(g)": -393.5,
|
| 313 |
+
"H2O(g)": -241.8,
|
| 314 |
+
"H2O(l)": -285.8,
|
| 315 |
+
"NH3(g)": -46.1, # Ammonia
|
| 316 |
+
"NO(g)": 90.3,
|
| 317 |
+
"NO2(g)": 33.2,
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
# A list of predefined, balanced chemical reactions.
|
| 321 |
+
REACTIONS = [
|
| 322 |
+
{
|
| 323 |
+
"name": "Combustion of Methane",
|
| 324 |
+
"equation": "CH4(g) + 2O2(g) → CO2(g) + 2H2O(l)",
|
| 325 |
+
"reactants": {"CH4(g)": 1, "O2(g)": 2},
|
| 326 |
+
"products": {"CO2(g)": 1, "H2O(l)": 2}
|
| 327 |
+
},
|
| 328 |
+
{
|
| 329 |
+
"name": "Combustion of Propane",
|
| 330 |
+
"equation": "C3H8(g) + 5O2(g) → 3CO2(g) + 4H2O(l)",
|
| 331 |
+
"reactants": {"C3H8(g)": 1, "O2(g)": 5},
|
| 332 |
+
"products": {"CO2(g)": 3, "H2O(l)": 4}
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"name": "Oxidation of Ammonia",
|
| 336 |
+
"equation": "4NH3(g) + 5O2(g) → 4NO(g) + 6H2O(g)",
|
| 337 |
+
"reactants": {"NH3(g)": 4, "O2(g)": 5},
|
| 338 |
+
"products": {"NO(g)": 4, "H2O(g)": 6}
|
| 339 |
+
},
|
| 340 |
+
{
|
| 341 |
+
"name": "Steam Reforming of Methane",
|
| 342 |
+
"equation": "CH4(g) + H2O(g) → CO(g) + 3H2(g)",
|
| 343 |
+
"reactants": {"CH4(g)": 1, "H2O(g)": 1},
|
| 344 |
+
"products": {"CO(g)": 1, "H2(g)": 3}
|
| 345 |
+
}
|
| 346 |
+
]
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
# A dictionary of substances with their heat capacity parameters for the
|
| 350 |
+
# equation: Cp/R = A + B*T + C*T² + D*T⁻² where T is in Kelvin.
|
| 351 |
+
# Parameters are typically valid for temperature ranges around 298-1200K
|
| 352 |
+
CP_PARAMS = {
|
| 353 |
+
# Original entries
|
| 354 |
+
"CH4(g)": {"A": 1.702, "B": 9.081E-2, "C": -2.164E-5, "D": 0},
|
| 355 |
+
"CO2(g)": {"A": 5.457, "B": 1.045E-2, "C": 0, "D": -1.157E5},
|
| 356 |
+
"N2(g)": {"A": 3.280, "B": 0.593E-2, "C": 0, "D": 0.040E5},
|
| 357 |
+
"H2O(l)": {"A": 8.712, "B": 1.25E-2, "C": -0.18E-5, "D": 0},
|
| 358 |
+
"C2H5OH(g)": {"A": 3.518, "B": 20.001E-2, "C": -6.002E-5, "D": 0},
|
| 359 |
+
|
| 360 |
+
# Common gases
|
| 361 |
+
"O2(g)": {"A": 3.630, "B": 1.794E-2, "C": -0.658E-5, "D": 0.061E5},
|
| 362 |
+
"H2(g)": {"A": 3.249, "B": 0.422E-2, "C": 0, "D": 0.083E5},
|
| 363 |
+
"NH3(g)": {"A": 3.578, "B": 3.020E-2, "C": 0, "D": -0.186E5},
|
| 364 |
+
"CO(g)": {"A": 3.376, "B": 0.557E-2, "C": 0, "D": -0.031E5},
|
| 365 |
+
"H2S(g)": {"A": 3.931, "B": 1.490E-2, "C": 0, "D": -0.232E5},
|
| 366 |
+
|
| 367 |
+
# Hydrocarbons
|
| 368 |
+
"C2H6(g)": {"A": 1.131, "B": 19.225E-2, "C": -5.561E-5, "D": 0},
|
| 369 |
+
"C3H8(g)": {"A": 1.213, "B": 28.785E-2, "C": -8.824E-5, "D": 0},
|
| 370 |
+
"C4H10(g)": {"A": 1.935, "B": 36.915E-2, "C": -11.402E-5, "D": 0},
|
| 371 |
+
"C2H4(g)": {"A": 1.424, "B": 14.394E-2, "C": -4.392E-5, "D": 0},
|
| 372 |
+
"C2H2(g)": {"A": 6.132, "B": 8.914E-2, "C": -6.347E-5, "D": 0},
|
| 373 |
+
|
| 374 |
+
# Common liquids
|
| 375 |
+
"C6H6(l)": {"A": -0.206, "B": 39.064E-2, "C": -13.301E-5, "D": 0},
|
| 376 |
+
"C7H8(l)": {"A": 0.290, "B": 47.052E-2, "C": -15.716E-5, "D": 0},
|
| 377 |
+
"C3H6O(l)": {"A": 1.506, "B": 30.476E-2, "C": -9.127E-5, "D": 0},
|
| 378 |
+
"CH3OH(l)": {"A": 5.052, "B": 16.561E-2, "C": -3.761E-5, "D": 0},
|
| 379 |
+
"C6H14(l)": {"A": 2.738, "B": 45.854E-2, "C": -14.518E-5, "D": 0},
|
| 380 |
+
|
| 381 |
+
# Inorganic compounds
|
| 382 |
+
"SO2(g)": {"A": 5.699, "B": 0.801E-2, "C": 0, "D": -1.015E5},
|
| 383 |
+
"NO(g)": {"A": 3.387, "B": 0.669E-2, "C": 0, "D": 0.095E5},
|
| 384 |
+
"NO2(g)": {"A": 4.982, "B": 1.195E-2, "C": -0.792E-5, "D": -0.377E5},
|
| 385 |
+
"Cl2(g)": {"A": 4.442, "B": 0.089E-2, "C": 0, "D": -0.344E5},
|
| 386 |
+
"HCl(g)": {"A": 3.156, "B": 0.623E-2, "C": 0, "D": 0.151E5},
|
| 387 |
+
|
| 388 |
+
# Noble gases
|
| 389 |
+
"He(g)": {"A": 2.500, "B": 0, "C": 0, "D": 0},
|
| 390 |
+
"Ar(g)": {"A": 2.500, "B": 0, "C": 0, "D": 0},
|
| 391 |
+
"Ne(g)": {"A": 2.500, "B": 0, "C": 0, "D": 0},
|
| 392 |
+
|
| 393 |
+
# Additional common substances
|
| 394 |
+
"Air(g)": {"A": 3.355, "B": 0.575E-2, "C": 0, "D": -0.016E5},
|
| 395 |
+
"H2O(g)": {"A": 3.470, "B": 1.450E-2, "C": 0, "D": 0.121E5},
|
| 396 |
+
"H2SO4(l)": {"A": 2.850, "B": 13.400E-2, "C": 0, "D": 0},
|
| 397 |
+
"NaCl(s)": {"A": 5.526, "B": 1.963E-2, "C": 0, "D": -0.333E5},
|
| 398 |
+
"CaCO3(s)": {"A": 12.572, "B": 2.637E-2, "C": -3.120E-5, "D": -3.642E5}
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
# A list of predefined, balanced combustion reactions with theoretical air.
|
| 403 |
+
COMBUSTION_REACTIONS = [
|
| 404 |
+
{
|
| 405 |
+
"name": "Combustion of Methane",
|
| 406 |
+
"fuel": "Methane",
|
| 407 |
+
"equation": "CH4(g) + 2O2(g) + 7.52N2(g) → CO2(g) + 2H2O(g) + 7.52N2(g)",
|
| 408 |
+
"reactants": {"CH4(g)": 1, "O2(g)": 2, "N2(g)": 7.52},
|
| 409 |
+
"products": {"CO2(g)": 1, "H2O(g)": 2, "N2(g)": 7.52}
|
| 410 |
+
},
|
| 411 |
+
{
|
| 412 |
+
"name": "Combustion of Propane",
|
| 413 |
+
"fuel": "Propane",
|
| 414 |
+
"equation": "C3H8(g) + 5O2(g) + 18.8N2(g) → 3CO2(g) + 4H2O(g) + 18.8N2(g)",
|
| 415 |
+
"reactants": {"C3H8(g)": 1, "O2(g)": 5, "N2(g)": 18.8},
|
| 416 |
+
"products": {"CO2(g)": 3, "H2O(g)": 4, "N2(g)": 18.8}
|
| 417 |
+
},
|
| 418 |
+
{
|
| 419 |
+
"name": "Combustion of Hydrogen",
|
| 420 |
+
"fuel": "Hydrogen",
|
| 421 |
+
"equation": "H2(g) + 0.5O2(g) + 1.88N2(g) → H2O(g) + 1.88N2(g)",
|
| 422 |
+
"reactants": {"H2(g)": 1, "O2(g)": 0.5, "N2(g)": 1.88},
|
| 423 |
+
"products": {"H2O(g)": 1, "N2(g)": 1.88}
|
| 424 |
+
},
|
| 425 |
+
{
|
| 426 |
+
"name": "Combustion of Carbon Monoxide",
|
| 427 |
+
"fuel": "Carbon Monoxide",
|
| 428 |
+
"equation": "CO(g) + 0.5O2(g) + 1.88N2(g) → CO2(g) + 1.88N2(g)",
|
| 429 |
+
"reactants": {"CO(g)": 1, "O2(g)": 0.5, "N2(g)": 1.88},
|
| 430 |
+
"products": {"CO2(g)": 1, "N2(g)": 1.88}
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"name": "Combustion of Acetylene",
|
| 434 |
+
"fuel": "Acetylene",
|
| 435 |
+
"equation": "C2H2(g) + 2.5O2(g) + 9.4N2(g) → 2CO2(g) + H2O(g) + 9.4N2(g)",
|
| 436 |
+
"reactants": {"C2H2(g)": 1, "O2(g)": 2.5, "N2(g)": 9.4},
|
| 437 |
+
"products": {"CO2(g)": 2, "H2O(g)": 1, "N2(g)": 9.4}
|
| 438 |
+
},
|
| 439 |
+
{
|
| 440 |
+
"name": "Combustion of Ethane",
|
| 441 |
+
"fuel": "Ethane",
|
| 442 |
+
"equation": "C2H6(g) + 3.5O2(g) + 13.16N2(g) → 2CO2(g) + 3H2O(g) + 13.16N2(g)",
|
| 443 |
+
"reactants": {"C2H6(g)": 1, "O2(g)": 3.5, "N2(g)": 13.16},
|
| 444 |
+
"products": {"CO2(g)": 2, "H2O(g)": 3, "N2(g)": 13.16}
|
| 445 |
+
},
|
| 446 |
+
{
|
| 447 |
+
"name": "Combustion of Butane",
|
| 448 |
+
"fuel": "Butane",
|
| 449 |
+
"equation": "C4H10(g) + 6.5O2(g) + 24.44N2(g) → 4CO2(g) + 5H2O(g) + 24.44N2(g)",
|
| 450 |
+
"reactants": {"C4H10(g)": 1, "O2(g)": 6.5, "N2(g)": 24.44},
|
| 451 |
+
"products": {"CO2(g)": 4, "H2O(g)": 5, "N2(g)": 24.44}
|
| 452 |
+
},
|
| 453 |
+
{
|
| 454 |
+
"name": "Combustion of Octane",
|
| 455 |
+
"fuel": "Octane",
|
| 456 |
+
"equation": "C8H18(g) + 12.5O2(g) + 47.0N2(g) → 8CO2(g) + 9H2O(g) + 47.0N2(g)",
|
| 457 |
+
"reactants": {"C8H18(g)": 1, "O2(g)": 12.5, "N2(g)": 47.0},
|
| 458 |
+
"products": {"CO2(g)": 8, "H2O(g)": 9, "N2(g)": 47.0}
|
| 459 |
+
},
|
| 460 |
+
{
|
| 461 |
+
"name": "Combustion of Ammonia",
|
| 462 |
+
"fuel": "Ammonia",
|
| 463 |
+
"equation": "4NH3(g) + 3O2(g) + 11.28N2(g) → 2N2(g) + 6H2O(g) + 11.28N2(g)",
|
| 464 |
+
"reactants": {"NH3(g)": 4, "O2(g)": 3, "N2(g)": 11.28},
|
| 465 |
+
"products": {"N2(g)": 13.28, "H2O(g)": 6} # Total N2 = 2 + 11.28
|
| 466 |
+
},
|
| 467 |
+
{
|
| 468 |
+
"name": "Combustion of Methanol",
|
| 469 |
+
"fuel": "Methanol",
|
| 470 |
+
"equation": "CH3OH(g) + 1.5O2(g) + 5.64N2(g) → CO2(g) + 2H2O(g) + 5.64N2(g)",
|
| 471 |
+
"reactants": {"CH3OH(g)": 1, "O2(g)": 1.5, "N2(g)": 5.64},
|
| 472 |
+
"products": {"CO2(g)": 1, "H2O(g)": 2, "N2(g)": 5.64}
|
| 473 |
+
},
|
| 474 |
+
{
|
| 475 |
+
"name": "Combustion of Ethanol",
|
| 476 |
+
"fuel": "Ethanol",
|
| 477 |
+
"equation": "C2H5OH(g) + 3O2(g) + 11.28N2(g) → 2CO2(g) + 3H2O(g) + 11.28N2(g)",
|
| 478 |
+
"reactants": {"C2H5OH(g)": 1, "O2(g)": 3, "N2(g)": 11.28},
|
| 479 |
+
"products": {"CO2(g)": 2, "H2O(g)": 3, "N2(g)": 11.28}
|
| 480 |
+
}
|
| 481 |
+
]
|
| 482 |
+
|
| 483 |
+
|
| 484 |
+
# Properties are given at 20°C (293.15 K) and standard pressure (101.325 kPa) unless otherwise noted.
|
| 485 |
+
# Viscosity can vary between sources. Values chosen for typical textbook accuracy.
|
| 486 |
+
# Format: { "Name": (Density [kg/m³], Dynamic Viscosity [Pa·s]) }
|
| 487 |
+
COMMON_LIQUIDS = {
|
| 488 |
+
# Water and Common Solvents
|
| 489 |
+
"Water": (998.2, 1.002e-3),
|
| 490 |
+
"Seawater (3.5% salinity)": (1025, 1.07e-3), # Viscosity approx. 7% higher than pure water
|
| 491 |
+
"Ethanol": (789.4, 1.074e-3),
|
| 492 |
+
"Methanol": (791.3, 0.544e-3),
|
| 493 |
+
"Isopropyl Alcohol (IPA)": (781.8, 2.04e-3),
|
| 494 |
+
"Acetone": (784.5, 0.306e-3),
|
| 495 |
+
"Benzene": (876.5, 0.601e-3),
|
| 496 |
+
"Toluene": (866.9, 0.560e-3),
|
| 497 |
+
"Diethyl Ether": (713.4, 0.223e-3),
|
| 498 |
+
"n-Hexane": (654.8, 0.294e-3),
|
| 499 |
+
|
| 500 |
+
# Oils and Hydrocarbons
|
| 501 |
+
"Engine Oil (SAE 10W)": (870, 0.065),
|
| 502 |
+
"Engine Oil (SAE 30)": (891.0, 0.290),
|
| 503 |
+
"Engine Oil (SAE 50)": (902, 0.860),
|
| 504 |
+
"Gear Oil (SAE 90)": (915, 0.700),
|
| 505 |
+
"Crude Oil (light)": (850, 7.5e-3),
|
| 506 |
+
"Kerosene": (810, 1.6e-3),
|
| 507 |
+
"Diesel Fuel": (850, 3.5e-3),
|
| 508 |
+
"Gasoline": (745, 0.29e-3), # ~0.4-0.8 cP, often approximated to water's order of magnitude
|
| 509 |
+
|
| 510 |
+
# Organic & Food Grade Liquids
|
| 511 |
+
"Glycerol (100%)": (1261.3, 1.490),
|
| 512 |
+
"Olive Oil": (910, 0.081),
|
| 513 |
+
"Corn Syrup": (1380, 5.0), # Highly variable with concentration and temp
|
| 514 |
+
"Honey": (1420, 10.0), # Highly variable with type and temp
|
| 515 |
+
"Milk (whole)": (1035, 2.0e-3),
|
| 516 |
+
"Blood Plasma (human)": (1025, 1.5e-3),
|
| 517 |
+
"Blood (whole, human)": (1060, 4.0e-3), # Shear-thinning, this is an approx. value
|
| 518 |
+
|
| 519 |
+
# Cryogens & Liquefied Gases (at their boiling point @ 1 atm)
|
| 520 |
+
"Liquid Nitrogen (77 K)": (804, 0.158e-3), # Note: Temperature is 77 K (-196°C)
|
| 521 |
+
"Liquid Oxygen (90 K)": (1141, 0.189e-3), # Temperature is 90 K (-183°C)
|
| 522 |
+
|
| 523 |
+
# Metals and Inorganics
|
| 524 |
+
"Mercury": (13593, 1.526e-3),
|
| 525 |
+
"Sulfuric Acid (98%)": (1831, 25.4e-3),
|
| 526 |
+
"Ethylene Glycol": (1113.4, 16.1e-3), # Common antifreeze
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
# Properties are given at 20°C (293.15 K) and 1 atm (101.325 kPa) unless otherwise noted.
|
| 531 |
+
# Format: { "Name": (Density [kg/m³], Dynamic Viscosity [Pa·s]) }
|
| 532 |
+
COMMON_GASES = {
|
| 533 |
+
# Common Gases
|
| 534 |
+
"Air": (1.204, 1.81e-5),
|
| 535 |
+
"Nitrogen (N₂)": (1.165, 1.76e-5),
|
| 536 |
+
"Oxygen (O₂)": (1.331, 2.00e-5),
|
| 537 |
+
"Carbon Dioxide (CO₂)": (1.842, 1.47e-5), # Viscosity is temperature-dependent and increases for CO2
|
| 538 |
+
"Argon": (1.661, 2.23e-5),
|
| 539 |
+
"Helium": (0.166, 1.96e-5),
|
| 540 |
+
"Neon": (0.840, 3.18e-5),
|
| 541 |
+
"Krypton": (3.479, 2.55e-5),
|
| 542 |
+
"Xenon": (5.495, 2.28e-5), # Density high, but viscosity is similar to air
|
| 543 |
+
|
| 544 |
+
# Hydrocarbons
|
| 545 |
+
"Methane (CH₄)": (0.668, 1.09e-5),
|
| 546 |
+
"Ethane (C₂H₆)": (1.264, 9.15e-6),
|
| 547 |
+
"Propane (C₃H₈)": (1.880, 8.00e-6), # Note: Viscosity decreases slightly with molecular weight in this series
|
| 548 |
+
"Butane (C₄H₁₀)": (2.489, 7.50e-6),
|
| 549 |
+
"Natural Gas (approx.)": (0.700, 1.10e-5), # Modeled after methane
|
| 550 |
+
"Acetylene (C₂H₂)": (1.092, 9.80e-6),
|
| 551 |
+
|
| 552 |
+
# Other Common Gases
|
| 553 |
+
"Hydrogen (H₂)": (0.0838, 8.90e-6), # Lowest density, very low viscosity
|
| 554 |
+
"Steam (Water Vapor)": (0.747, 1.02e-5), # At 100°C (373 K), 1 atm
|
| 555 |
+
"Ammonia (NH₃)": (0.718, 1.01e-5),
|
| 556 |
+
"Chlorine (Cl₂)": (2.994, 1.33e-5),
|
| 557 |
+
"Sulfur Hexafluoride (SF₆)": (6.17, 1.59e-5), # High-density gas used in industry
|
| 558 |
+
|
| 559 |
+
# Noble Gases
|
| 560 |
+
"Radon": (9.23, 2.30e-5), # Theoretical value at 20°C; highly radioactive
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
# Molecular parameters for the Chapman-Enskog equation (Kinetic Theory)
|
| 565 |
+
# Sources: NIST, CRC Handbook, and standard chemical engineering texts.
|
| 566 |
+
# Format: { "Name": (Molar Mass [g/mol], Sigma σ [Å], Epsilon ε / k [K]) }
|
| 567 |
+
# Note: Epsilon ε / k (the Lennard-Jones energy parameter) is included for calculating the collision integral.
|
| 568 |
+
GAS_MOLECULAR_PARAMS = {
|
| 569 |
+
"Air": (28.97, 3.62, 97.0),
|
| 570 |
+
"Nitrogen (N₂)": (28.01, 3.70, 95.05),
|
| 571 |
+
"Oxygen (O₂)": (32.00, 3.46, 106.7),
|
| 572 |
+
"Carbon Dioxide (CO₂)": (44.01, 3.94, 195.2),
|
| 573 |
+
"Argon": (39.95, 3.54, 93.3),
|
| 574 |
+
"Helium": (4.003, 2.55, 10.22),
|
| 575 |
+
"Neon": (20.18, 2.92, 32.8),
|
| 576 |
+
"Krypton": (83.80, 3.69, 178.9),
|
| 577 |
+
"Xenon": (131.29, 4.10, 231.0),
|
| 578 |
+
"Methane (CH₄)": (16.04, 3.78, 148.6),
|
| 579 |
+
"Ethane (C₂H₆)": (30.07, 4.42, 215.7),
|
| 580 |
+
"Propane (C₃H₈)": (44.10, 5.06, 237.1),
|
| 581 |
+
"Butane (C₄H₁₀)": (58.12, 5.47, 531.4), # n-butane
|
| 582 |
+
"Hydrogen (H₂)": (2.016, 2.93, 33.3),
|
| 583 |
+
"Ammonia (NH₃)": (17.03, 2.92, 558.3), # Polar molecule, value is an effective fit.
|
| 584 |
+
"Chlorine (Cl₂)": (70.90, 4.40, 316.0),
|
| 585 |
+
"Sulfur Hexafluoride (SF₆)": (146.06, 5.51, 222.1),
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
# Properties for non-Newtonian power-law fluids
|
| 590 |
+
# Format: { "Name": (Consistency Index K [Pa·s^n], Power-Law Index n [dimensionless]) }
|
| 591 |
+
POWER_LAW_FLUIDS = {
|
| 592 |
+
# Common Household & Food (Shear-Thinning)
|
| 593 |
+
"Ketchup": (32.5, 0.22),
|
| 594 |
+
"Applesauce": (15.0, 0.3),
|
| 595 |
+
"Mustard": (50.0, 0.28),
|
| 596 |
+
"Mayonnaise": (85.0, 0.6),
|
| 597 |
+
"Tomato Puree": (25.0, 0.5),
|
| 598 |
+
"Yogurt": (12.5, 0.6),
|
| 599 |
+
"Toothpaste": (120.0, 0.4),
|
| 600 |
+
"Shampoo": (25.0, 0.6),
|
| 601 |
+
"Hand Lotion": (80.0, 0.5),
|
| 602 |
+
|
| 603 |
+
# Paints & Inks (Shear-Thinning)
|
| 604 |
+
"Latex Paint": (45.0, 0.45),
|
| 605 |
+
"Printing Ink": (10.0, 0.7),
|
| 606 |
+
|
| 607 |
+
# Biological Fluids (Mostly Shear-Thinning)
|
| 608 |
+
"Blood (Plasma)": (0.012, 0.95), # Very low K, nearly Newtonian
|
| 609 |
+
"Mucus": (10.0, 0.5),
|
| 610 |
+
|
| 611 |
+
# Polymer Solutions & Melts (Shear-Thinning)
|
| 612 |
+
"0.5% Carboxymethylcellulose (CMC) in Water": (1.5, 0.6),
|
| 613 |
+
"1.5% Polyacrylamide in Water": (5.0, 0.3),
|
| 614 |
+
"Molten Polyethylene": (5000.0, 0.6), # K is very temperature-dependent
|
| 615 |
+
|
| 616 |
+
# Newtonian Baseline (n = 1)
|
| 617 |
+
"Water": (0.001, 1.0), # K is the dynamic viscosity
|
| 618 |
+
"Glycerol": (1.0, 1.0),
|
| 619 |
+
"Air": (1.8e-5, 1.0), # K is the dynamic viscosity
|
| 620 |
+
|
| 621 |
+
# Shear-Thickening (Dilatant)
|
| 622 |
+
"Corn Starch Suspension (40%)": (2.0, 1.9),
|
| 623 |
+
"Corn Starch Suspension (50%)": (10.0, 2.5), # Higher concentration -> stronger effect
|
| 624 |
+
"Silica Sand Suspension (60%)": (0.5, 1.6),
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
# Standard gravitational acceleration in m/s²
|
| 629 |
+
GRAVITATIONAL_ACCELERATION = 9.81
|
data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/conversion_and_reactor_sizing.cpython-311.pyc
ADDED
|
Binary file (11.9 kB). View file
|
|
|
data/templates/branches/chemical_engineering/reaction_kinetics/__pycache__/mole_balances.cpython-311.pyc
ADDED
|
Binary file (19.3 kB). View file
|
|
|
data/templates/branches/chemical_engineering/reaction_kinetics/conversion_and_reactor_sizing.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import numpy as np
|
| 3 |
+
from data.templates.branches.chemical_engineering.constants import GENERAL_REACTANTS
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# Template 1 (Advanced)
|
| 7 |
+
def template_levenspiel_plot_interpretation():
|
| 8 |
+
"""
|
| 9 |
+
Levenspiel Plot Data Interpretation for Reactor Volume Calculation
|
| 10 |
+
|
| 11 |
+
Scenario:
|
| 12 |
+
Experimental data of 1/(-r_A) vs. conversion (X) is provided in tabulated form.
|
| 13 |
+
The goal is to determine the reactor volumes required to achieve a target
|
| 14 |
+
conversion without explicitly knowing the rate law. Two cases are considered:
|
| 15 |
+
|
| 16 |
+
- Continuous Stirred-Tank Reactor (CSTR):
|
| 17 |
+
Volume is calculated using the rectangular approximation:
|
| 18 |
+
V_CSTR = F_A0 * X * [1/(-r_A)]_exit
|
| 19 |
+
|
| 20 |
+
- Plug Flow Reactor (PFR):
|
| 21 |
+
Volume is calculated using trapezoidal integration of the curve:
|
| 22 |
+
V_PFR = F_A0 * ∫₀ˣ (1/(-r_A)) dX
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
tuple: A tuple containing:
|
| 26 |
+
- str: A question asking to compute both CSTR and PFR volumes from tabulated data.
|
| 27 |
+
- str: A step-by-step solution showing the calculations, including trapezoidal rule details.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
# 1. Generate variable parameters with validation
|
| 31 |
+
reactant_name = random.choice(GENERAL_REACTANTS)
|
| 32 |
+
F_A0 = round(random.uniform(1.0, 5.0), 2) # mol/s
|
| 33 |
+
|
| 34 |
+
# Ensure F_A0 is positive
|
| 35 |
+
if F_A0 <= 0:
|
| 36 |
+
F_A0 = 2.0 # Default fallback
|
| 37 |
+
|
| 38 |
+
# Variable target conversion instead of fixed 0.8
|
| 39 |
+
target_conversion_options = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9]
|
| 40 |
+
target_conversion = random.choice(target_conversion_options)
|
| 41 |
+
|
| 42 |
+
# 2. Generate conversion points dynamically based on target conversion
|
| 43 |
+
if target_conversion <= 0.6:
|
| 44 |
+
num_points = 7
|
| 45 |
+
elif target_conversion <= 0.8:
|
| 46 |
+
num_points = 9
|
| 47 |
+
else:
|
| 48 |
+
num_points = 10 # More points for higher conversions
|
| 49 |
+
|
| 50 |
+
X_values = np.linspace(0.0, target_conversion, num_points)
|
| 51 |
+
X_values = np.round(X_values, 2) # Clean up floating point artifacts
|
| 52 |
+
|
| 53 |
+
# 3. Generate realistic 1/(-r_A) values with proper validation
|
| 54 |
+
# Base pattern that increases with conversion (typical behavior)
|
| 55 |
+
base_pattern = 1.5 + 2.0 * X_values + 3.0 * X_values**2
|
| 56 |
+
|
| 57 |
+
# Add controlled randomness
|
| 58 |
+
random_factor = random.uniform(0.8, 1.4)
|
| 59 |
+
noise = 1 + 0.15 * np.random.uniform(-1, 1, num_points)
|
| 60 |
+
|
| 61 |
+
# Ensure noise doesn't make values negative or too small
|
| 62 |
+
noise = np.maximum(noise, 0.5)
|
| 63 |
+
|
| 64 |
+
inv_rate_values = base_pattern * random_factor * noise
|
| 65 |
+
inv_rate_values = np.round(inv_rate_values, 2)
|
| 66 |
+
|
| 67 |
+
# 4. Data validation and correction
|
| 68 |
+
# Ensure first value is reasonable (at X=0)
|
| 69 |
+
if inv_rate_values[0] < 1.0:
|
| 70 |
+
inv_rate_values[0] = round(random.uniform(1.0, 2.5), 2)
|
| 71 |
+
|
| 72 |
+
# Ensure all values are positive
|
| 73 |
+
inv_rate_values = np.maximum(inv_rate_values, 0.5)
|
| 74 |
+
|
| 75 |
+
# Enforce general increasing trend (physical expectation)
|
| 76 |
+
for i in range(1, len(inv_rate_values)):
|
| 77 |
+
if inv_rate_values[i] < inv_rate_values[i-1]:
|
| 78 |
+
inv_rate_values[i] = inv_rate_values[i-1] + round(random.uniform(0.1, 0.3), 2)
|
| 79 |
+
|
| 80 |
+
# Final validation - ensure no extremely large values
|
| 81 |
+
inv_rate_values = np.minimum(inv_rate_values, 50.0)
|
| 82 |
+
|
| 83 |
+
# 5. Calculate CSTR volume with error checking
|
| 84 |
+
try:
|
| 85 |
+
inv_rate_at_final = inv_rate_values[-1] # 1/(-r_A) at final X
|
| 86 |
+
X_final = X_values[-1]
|
| 87 |
+
|
| 88 |
+
if inv_rate_at_final <= 0:
|
| 89 |
+
inv_rate_at_final = 5.0 # Fallback value
|
| 90 |
+
|
| 91 |
+
V_CSTR = F_A0 * inv_rate_at_final * X_final
|
| 92 |
+
|
| 93 |
+
# Ensure positive volume
|
| 94 |
+
if V_CSTR <= 0:
|
| 95 |
+
V_CSTR = F_A0 * 5.0 * X_final # Fallback calculation
|
| 96 |
+
|
| 97 |
+
except Exception:
|
| 98 |
+
# Fallback CSTR calculation
|
| 99 |
+
V_CSTR = F_A0 * 8.0 * target_conversion
|
| 100 |
+
|
| 101 |
+
# 6. Calculate PFR volume with error checking
|
| 102 |
+
try:
|
| 103 |
+
# Validate data before integration
|
| 104 |
+
if len(X_values) != len(inv_rate_values):
|
| 105 |
+
raise ValueError("Data arrays have mismatched lengths")
|
| 106 |
+
|
| 107 |
+
if np.any(inv_rate_values <= 0):
|
| 108 |
+
raise ValueError("Negative or zero rate values detected")
|
| 109 |
+
|
| 110 |
+
# Check for monotonic X values
|
| 111 |
+
if not np.all(np.diff(X_values) >= 0):
|
| 112 |
+
raise ValueError("X values not monotonically increasing")
|
| 113 |
+
|
| 114 |
+
area_under_curve = np.trapezoid(inv_rate_values, X_values)
|
| 115 |
+
|
| 116 |
+
if area_under_curve <= 0:
|
| 117 |
+
raise ValueError("Negative area under curve")
|
| 118 |
+
|
| 119 |
+
V_PFR = F_A0 * area_under_curve
|
| 120 |
+
|
| 121 |
+
# Sanity check on PFR volume
|
| 122 |
+
if V_PFR <= 0 or V_PFR > 10 * V_CSTR:
|
| 123 |
+
raise ValueError("PFR volume unrealistic")
|
| 124 |
+
|
| 125 |
+
except Exception:
|
| 126 |
+
# Fallback PFR calculation using simple approximation
|
| 127 |
+
avg_inv_rate = np.mean(inv_rate_values) if len(inv_rate_values) > 0 else 5.0
|
| 128 |
+
V_PFR = F_A0 * avg_inv_rate * target_conversion * 0.7 # Approximate PFR advantage
|
| 129 |
+
|
| 130 |
+
# 7. Final physical validation
|
| 131 |
+
if V_CSTR <= 0:
|
| 132 |
+
V_CSTR = abs(V_CSTR) + 1.0
|
| 133 |
+
if V_PFR <= 0:
|
| 134 |
+
V_PFR = abs(V_PFR) + 1.0
|
| 135 |
+
|
| 136 |
+
# Ensure PFR is typically more efficient (but allow exceptions)
|
| 137 |
+
if V_PFR > 1.5 * V_CSTR:
|
| 138 |
+
# Unusual case - might indicate data issues, but proceed with warning
|
| 139 |
+
unusual_case = True
|
| 140 |
+
else:
|
| 141 |
+
unusual_case = False
|
| 142 |
+
|
| 143 |
+
# 8. Create data table for display
|
| 144 |
+
data_table = "X\t1/(-r_A) [L·s/mol]\n"
|
| 145 |
+
data_table += "-" * 25 + "\n"
|
| 146 |
+
for x, inv_r in zip(X_values, inv_rate_values):
|
| 147 |
+
data_table += f"{x:.2f}\t{inv_r:.2f}\n"
|
| 148 |
+
|
| 149 |
+
# 9. Generate question
|
| 150 |
+
question = (
|
| 151 |
+
f"A reaction involving {reactant_name} (A → products) is being studied for reactor design. "
|
| 152 |
+
f"Experimental data has been collected and plotted as a Levenspiel plot (1/(-r_A) vs X). "
|
| 153 |
+
f"The inlet molar flow rate is {F_A0} mol/s. Using the data below, calculate:\n\n"
|
| 154 |
+
f"a) The volume of a CSTR required to achieve {target_conversion*100:.0f}% conversion\n"
|
| 155 |
+
f"b) The volume of a PFR required to achieve {target_conversion*100:.0f}% conversion\n\n"
|
| 156 |
+
f"Levenspiel Plot Data:\n"
|
| 157 |
+
f"{data_table}"
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
# 10. Generate solution
|
| 161 |
+
solution = (
|
| 162 |
+
f"**Given:**\n"
|
| 163 |
+
f"- Reactant: {reactant_name}\n"
|
| 164 |
+
f"- Inlet molar flow rate: F_A0 = {F_A0} mol/s\n"
|
| 165 |
+
f"- Target conversion: X = {target_conversion} ({target_conversion*100:.0f}%)\n"
|
| 166 |
+
f"- Data points: {len(X_values)} experimental values\n\n"
|
| 167 |
+
|
| 168 |
+
f"**Part (a): CSTR Volume Calculation**\n\n"
|
| 169 |
+
f"**Step 1:** For a CSTR, the design equation is:\n"
|
| 170 |
+
f"V_CSTR = F_A0 × X × [1/(-r_A)]_exit\n\n"
|
| 171 |
+
|
| 172 |
+
f"**Step 2:** From the data table, at X = {target_conversion}:\n"
|
| 173 |
+
f"[1/(-r_A)]_at_X={target_conversion} = {inv_rate_at_final:.2f} L·s/mol\n\n"
|
| 174 |
+
|
| 175 |
+
f"**Step 3:** Calculate CSTR volume:\n"
|
| 176 |
+
f"V_CSTR = {F_A0} mol/s × {target_conversion} × {inv_rate_at_final:.2f} L·s/mol\n"
|
| 177 |
+
f"V_CSTR = {round(V_CSTR, 2)} L\n\n"
|
| 178 |
+
|
| 179 |
+
f"**Part (b): PFR Volume Calculation**\n\n"
|
| 180 |
+
f"**Step 1:** For a PFR, the design equation is:\n"
|
| 181 |
+
f"V_PFR = F_A0 × ∫[0 to X] (1/(-r_A)) dX\n\n"
|
| 182 |
+
|
| 183 |
+
f"**Step 2:** The integral represents the area under the Levenspiel plot curve.\n"
|
| 184 |
+
f"Using trapezoidal rule for numerical integration:\n\n"
|
| 185 |
+
|
| 186 |
+
f"**Step 3:** Apply trapezoidal rule to the data points:\n"
|
| 187 |
+
f"Area = Σ[(X_i+1 - X_i) × (y_i + y_i+1)/2]\n"
|
| 188 |
+
f"where y_i = [1/(-r_A)]_i\n\n"
|
| 189 |
+
|
| 190 |
+
f"**Step 4:** Calculate individual trapezoid areas:\n"
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
# Add detailed trapezoidal calculation with error handling
|
| 194 |
+
total_area = 0
|
| 195 |
+
try:
|
| 196 |
+
for i in range(len(X_values)-1):
|
| 197 |
+
dx = X_values[i+1] - X_values[i]
|
| 198 |
+
avg_height = (inv_rate_values[i] + inv_rate_values[i+1]) / 2
|
| 199 |
+
trap_area = dx * avg_height
|
| 200 |
+
|
| 201 |
+
# Validate each trapezoid calculation
|
| 202 |
+
if dx <= 0 or avg_height <= 0:
|
| 203 |
+
continue # Skip invalid intervals
|
| 204 |
+
|
| 205 |
+
total_area += trap_area
|
| 206 |
+
|
| 207 |
+
solution += (
|
| 208 |
+
f"Interval [{X_values[i]:.2f} to {X_values[i+1]:.2f}]: "
|
| 209 |
+
f"ΔX = {dx:.2f}, Avg height = ({inv_rate_values[i]:.2f} + {inv_rate_values[i+1]:.2f})/2 = {avg_height:.2f}\n"
|
| 210 |
+
f"Area = {dx:.2f} × {avg_height:.2f} = {trap_area:.3f}\n"
|
| 211 |
+
)
|
| 212 |
+
except Exception:
|
| 213 |
+
# Fallback area calculation
|
| 214 |
+
total_area = area_under_curve if 'area_under_curve' in locals() else np.mean(inv_rate_values) * target_conversion
|
| 215 |
+
|
| 216 |
+
solution += (
|
| 217 |
+
f"\n**Step 5:** Total area under curve:\n"
|
| 218 |
+
f"Total area = {total_area:.3f}\n\n"
|
| 219 |
+
|
| 220 |
+
f"**Step 6:** Calculate PFR volume:\n"
|
| 221 |
+
f"V_PFR = F_A0 × Area = {F_A0} mol/s × {total_area:.3f}\n"
|
| 222 |
+
f"V_PFR = {round(V_PFR, 2)} L\n\n"
|
| 223 |
+
|
| 224 |
+
f"**Final Answers:**\n"
|
| 225 |
+
f"a) CSTR Volume = {round(V_CSTR, 2)} L\n"
|
| 226 |
+
f"b) PFR Volume = {round(V_PFR, 2)} L\n\n"
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
# Add appropriate comparison note based on results
|
| 230 |
+
if unusual_case:
|
| 231 |
+
comparison_note = (
|
| 232 |
+
f"**Note:** In this case, the PFR volume ({round(V_PFR, 2)} L) is unusually large compared to "
|
| 233 |
+
f"the CSTR volume ({round(V_CSTR, 2)} L). This could indicate very flat kinetics or "
|
| 234 |
+
f"experimental measurement uncertainty."
|
| 235 |
+
)
|
| 236 |
+
elif V_PFR < V_CSTR:
|
| 237 |
+
efficiency = ((V_CSTR - V_PFR) / V_CSTR) * 100
|
| 238 |
+
comparison_note = (
|
| 239 |
+
f"**Note:** The PFR requires less volume than the CSTR ({round(V_PFR, 2)} L vs {round(V_CSTR, 2)} L), "
|
| 240 |
+
f"providing a {efficiency:.1f}% volume reduction due to more efficient use of reaction kinetics."
|
| 241 |
+
)
|
| 242 |
+
else:
|
| 243 |
+
comparison_note = (
|
| 244 |
+
f"**Note:** The reactor volumes are similar ({round(V_CSTR, 2)} L vs {round(V_PFR, 2)} L), "
|
| 245 |
+
f"suggesting relatively flat kinetics over this conversion range."
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
solution += comparison_note
|
| 249 |
+
|
| 250 |
+
return question, solution
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def main():
|
| 254 |
+
"""
|
| 255 |
+
Generate numerous instances of the Levenspiel plot template with different random seeds
|
| 256 |
+
and write the results to a JSONL file.
|
| 257 |
+
"""
|
| 258 |
+
|
| 259 |
+
import json
|
| 260 |
+
import os
|
| 261 |
+
|
| 262 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 263 |
+
output_file = "testset/chemical_engineering/reaction_kinetics/conversion_and_reactor_sizing.jsonl"
|
| 264 |
+
|
| 265 |
+
# Create the directory if it doesn't exist
|
| 266 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 267 |
+
|
| 268 |
+
# List of template functions with their ID and level
|
| 269 |
+
templates = [
|
| 270 |
+
(template_levenspiel_plot_interpretation, "levenspiel_plot_interpretation", "Advanced")
|
| 271 |
+
]
|
| 272 |
+
|
| 273 |
+
# List to store all generated problems
|
| 274 |
+
all_problems = []
|
| 275 |
+
|
| 276 |
+
# Generate problems for each template in the list
|
| 277 |
+
for template_func, id_name, level in templates:
|
| 278 |
+
for _ in range(50):
|
| 279 |
+
# Generate a unique seed for reproducibility
|
| 280 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 281 |
+
random.seed(seed)
|
| 282 |
+
|
| 283 |
+
# Generate the question and solution by calling the function
|
| 284 |
+
question, solution = template_func()
|
| 285 |
+
|
| 286 |
+
# Create a dictionary entry for the problem
|
| 287 |
+
problem_entry = {
|
| 288 |
+
"seed": seed,
|
| 289 |
+
"branch": "chemical_engineering",
|
| 290 |
+
"domain": "reaction_kinetics",
|
| 291 |
+
"area": "conversion_and_reactor_sizing",
|
| 292 |
+
"id": id_name,
|
| 293 |
+
"level": level,
|
| 294 |
+
"question": question,
|
| 295 |
+
"solution": solution
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
# Add the new problem to our list
|
| 299 |
+
all_problems.append(problem_entry)
|
| 300 |
+
|
| 301 |
+
# Write all generated problems to a .jsonl file (JSON Lines format)
|
| 302 |
+
with open(output_file, "w") as file:
|
| 303 |
+
for problem in all_problems:
|
| 304 |
+
file.write(json.dumps(problem))
|
| 305 |
+
file.write("\n")
|
| 306 |
+
|
| 307 |
+
print(f"\nSuccess! Generated {len(all_problems)} problems and saved them to '{output_file}'")
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
if __name__ == "__main__":
|
| 311 |
+
main()
|
data/templates/branches/chemical_engineering/reaction_kinetics/mole_balances.py
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
from scipy.integrate import quad
|
| 4 |
+
from data.templates.branches.chemical_engineering.constants import LIQUID_PHASE_REACTANTS, GENERAL_REACTANTS
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# Template 1 (Easy)
|
| 8 |
+
def template_cstr_volume_basic():
|
| 9 |
+
"""
|
| 10 |
+
CSTR Volume Calculation
|
| 11 |
+
|
| 12 |
+
Scenario:
|
| 13 |
+
The General Mole Balance for a Continuous Stirred-Tank Reactor (CSTR) at steady
|
| 14 |
+
state is used to determine the necessary reactor volume to achieve a desired
|
| 15 |
+
outcome. Given the inlet and outlet molar flow rates and the reaction rate,
|
| 16 |
+
the goal is to compute the volume using:
|
| 17 |
+
|
| 18 |
+
V = (F_A0 - F_A) / (-r_A)
|
| 19 |
+
|
| 20 |
+
Returns:
|
| 21 |
+
tuple: A tuple containing:
|
| 22 |
+
- str: A question asking to compute the CSTR volume.
|
| 23 |
+
- str: A step-by-step solution showing the calculation (the Python trace).
|
| 24 |
+
"""
|
| 25 |
+
# 1. Parameterize the inputs with random values
|
| 26 |
+
reactant_name = random.choice(GENERAL_REACTANTS)
|
| 27 |
+
# Inlet molar flow rate in mol/s
|
| 28 |
+
F_A0 = round(random.uniform(1.0, 5.0), 2)
|
| 29 |
+
# Outlet molar flow rate in mol/s (must be less than inlet)
|
| 30 |
+
F_A = round(random.uniform(0.1, F_A0 - 0.1), 2)
|
| 31 |
+
# Reaction rate in mol/(L·s) - a positive value for -r_A
|
| 32 |
+
neg_r_A = round(random.uniform(0.01, 0.05), 4)
|
| 33 |
+
|
| 34 |
+
# 2. Perform the core calculation
|
| 35 |
+
volume = (F_A0 - F_A) / neg_r_A
|
| 36 |
+
|
| 37 |
+
# 3. Generate the question and solution strings
|
| 38 |
+
question = (
|
| 39 |
+
f"A steady-state CSTR is used for the consumption of {reactant_name}. "
|
| 40 |
+
f"The inlet molar flow rate of {reactant_name} is {F_A0} mol/s, and the desired "
|
| 41 |
+
f"outlet molar flow rate is {F_A} mol/s. If the rate of reaction is {neg_r_A} mol/(L·s), "
|
| 42 |
+
f"what is the required reactor volume in liters?"
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
solution = (
|
| 46 |
+
f"**Step 1:** State the CSTR design equation.\n"
|
| 47 |
+
f" V = (F_A0 - F_A) / (-r_A)\n\n"
|
| 48 |
+
f"**Step 2:** Substitute the given values into the equation.\n"
|
| 49 |
+
f" V = ({F_A0} mol/s - {F_A} mol/s) / ({neg_r_A} mol/(L·s))\n\n"
|
| 50 |
+
f"**Step 3:** Calculate the final volume.\n"
|
| 51 |
+
f" V = {round(volume, 2)} L\n\n"
|
| 52 |
+
f"**Answer:** The required reactor volume is {round(volume, 2)} liters."
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
return question, solution
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# Template 2 (Easy)
|
| 59 |
+
def template_batch_reactor_zero_order():
|
| 60 |
+
"""
|
| 61 |
+
Batch Reactor Time Calculation - Zero Order Kinetics
|
| 62 |
+
|
| 63 |
+
Scenario:
|
| 64 |
+
The time required for a reaction in a constant-volume batch reactor is
|
| 65 |
+
determined by integrating the mole balance equation. For a zero-order
|
| 66 |
+
reaction, the rate of reaction is constant and independent of concentration.
|
| 67 |
+
Given the initial and final concentrations and the rate constant, the
|
| 68 |
+
goal is to compute the necessary time using the integrated rate law:
|
| 69 |
+
|
| 70 |
+
t = (C_A0 - C_A) / k
|
| 71 |
+
|
| 72 |
+
Returns:
|
| 73 |
+
tuple: A tuple containing:
|
| 74 |
+
- str: A question asking to calculate the required reaction time.
|
| 75 |
+
- str: A step-by-step solution showing the derivation and calculation.
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
# 1. Parameterize inputs
|
| 79 |
+
reactant_name = random.choice(LIQUID_PHASE_REACTANTS)
|
| 80 |
+
C_A0 = round(random.uniform(2.0, 10.0), 2) # Initial concentration (mol/L)
|
| 81 |
+
|
| 82 |
+
# Generate final concentration ensuring reasonable conversion
|
| 83 |
+
conversion = round(random.uniform(0.2, 0.8), 2)
|
| 84 |
+
C_A = round(C_A0 * (1 - conversion), 2)
|
| 85 |
+
|
| 86 |
+
# Zero-order rate constant (mol/(L·s))
|
| 87 |
+
k = round(random.uniform(0.01, 0.1), 4)
|
| 88 |
+
|
| 89 |
+
# Calculate time
|
| 90 |
+
time = (C_A0 - C_A) / k
|
| 91 |
+
|
| 92 |
+
# Generate question and solution
|
| 93 |
+
question = (
|
| 94 |
+
f"A zero-order liquid-phase reaction involving {reactant_name} is carried out "
|
| 95 |
+
f"in a constant-volume batch reactor. The initial concentration is {C_A0} mol/L, "
|
| 96 |
+
f"and the desired final concentration is {C_A} mol/L. If the zero-order rate "
|
| 97 |
+
f"constant is {k} mol/(L·s), calculate the required reaction time."
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
solution = (
|
| 101 |
+
f"**Given:**\n"
|
| 102 |
+
f"- Initial concentration (C_A0) = {C_A0} mol/L\n"
|
| 103 |
+
f"- Final concentration (C_A) = {C_A} mol/L\n"
|
| 104 |
+
f"- Zero-order rate constant (k) = {k} mol/(L·s)\n"
|
| 105 |
+
f"- Conversion = {round(conversion * 100, 1)}%\n\n"
|
| 106 |
+
|
| 107 |
+
f"**Step 1:** Write the mole balance for a constant-volume batch reactor.\n"
|
| 108 |
+
f"dC_A/dt = r_A\n\n"
|
| 109 |
+
|
| 110 |
+
f"**Step 2:** For zero-order kinetics, r_A = -k (constant).\n"
|
| 111 |
+
f"dC_A/dt = -k\n\n"
|
| 112 |
+
|
| 113 |
+
f"**Step 3:** Integrate from t=0 (C_A = C_A0) to t=t (C_A = C_A).\n"
|
| 114 |
+
f"∫[C_A0 to C_A] dC_A = -k ∫[0 to t] dt\n"
|
| 115 |
+
f"C_A - C_A0 = -k × t\n\n"
|
| 116 |
+
|
| 117 |
+
f"**Step 4:** Solve for time.\n"
|
| 118 |
+
f"t = (C_A0 - C_A) / k\n\n"
|
| 119 |
+
|
| 120 |
+
f"**Step 5:** Substitute values.\n"
|
| 121 |
+
f"t = ({C_A0} - {C_A}) mol/L / {k} mol/(L·s)\n"
|
| 122 |
+
f"t = {C_A0 - C_A} / {k} = {round(time, 2)} s\n\n"
|
| 123 |
+
|
| 124 |
+
f"**Answer:** The required reaction time is {round(time, 2)} seconds."
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
return question, solution
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# Template 3 (Intermediate)
|
| 131 |
+
def template_batch_reactor_first_order():
|
| 132 |
+
"""
|
| 133 |
+
Batch Reactor Time Calculation - First Order Kinetics
|
| 134 |
+
|
| 135 |
+
Scenario:
|
| 136 |
+
This template calculates the time for a reaction in a constant-volume
|
| 137 |
+
batch reactor following first-order kinetics. Unlike zero-order reactions,
|
| 138 |
+
the rate of a first-order reaction is directly proportional to the
|
| 139 |
+
concentration of the reactant (-r_A = k*C_A). The required time is found
|
| 140 |
+
by integrating the mole balance equation. Given the initial and final
|
| 141 |
+
concentrations and the rate constant, the goal is to compute the time using:
|
| 142 |
+
|
| 143 |
+
t = (1/k) * ln(C_A0 / C_A)
|
| 144 |
+
|
| 145 |
+
Returns:
|
| 146 |
+
tuple: A tuple containing:
|
| 147 |
+
- str: A question asking to calculate the required reaction time.
|
| 148 |
+
- str: A step-by-step solution showing the derivation and calculation.
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
reactant_name = random.choice(LIQUID_PHASE_REACTANTS)
|
| 152 |
+
C_A0 = round(random.uniform(1.0, 5.0), 2)
|
| 153 |
+
|
| 154 |
+
conversion = round(random.uniform(0.3, 0.9), 2)
|
| 155 |
+
C_A = round(C_A0 * (1 - conversion), 2)
|
| 156 |
+
|
| 157 |
+
# First-order rate constant (1/s)
|
| 158 |
+
k = round(random.uniform(0.001, 0.01), 5)
|
| 159 |
+
|
| 160 |
+
# Calculate time
|
| 161 |
+
time = math.log(C_A0 / C_A) / k
|
| 162 |
+
|
| 163 |
+
question = (
|
| 164 |
+
f"A first-order liquid-phase reaction of {reactant_name} occurs in a batch reactor. "
|
| 165 |
+
f"The initial concentration is {C_A0} mol/L, and after reaction, the concentration "
|
| 166 |
+
f"decreases to {C_A} mol/L. If the first-order rate constant is {k} s⁻¹, "
|
| 167 |
+
f"determine the reaction time required."
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
solution = (
|
| 171 |
+
f"**Given:**\n"
|
| 172 |
+
f"- Initial concentration (C_A0) = {C_A0} mol/L\n"
|
| 173 |
+
f"- Final concentration (C_A) = {C_A} mol/L\n"
|
| 174 |
+
f"- First-order rate constant (k) = {k} s⁻¹\n"
|
| 175 |
+
f"- Conversion = {round(conversion * 100, 1)}%\n\n"
|
| 176 |
+
|
| 177 |
+
f"**Step 1:** Write the rate law for first-order kinetics.\n"
|
| 178 |
+
f"-r_A = k × C_A\n\n"
|
| 179 |
+
|
| 180 |
+
f"**Step 2:** Set up the mole balance equation.\n"
|
| 181 |
+
f"dC_A/dt = -k × C_A\n\n"
|
| 182 |
+
|
| 183 |
+
f"**Step 3:** Separate variables and integrate.\n"
|
| 184 |
+
f"dC_A/C_A = -k × dt\n"
|
| 185 |
+
f"∫[C_A0 to C_A] dC_A/C_A = -k ∫[0 to t] dt\n\n"
|
| 186 |
+
|
| 187 |
+
f"**Step 4:** Solve the integral.\n"
|
| 188 |
+
f"ln(C_A/C_A0) = -k × t\n"
|
| 189 |
+
f"ln(C_A0/C_A) = k × t\n\n"
|
| 190 |
+
|
| 191 |
+
f"**Step 5:** Solve for time.\n"
|
| 192 |
+
f"t = (1/k) × ln(C_A0/C_A)\n\n"
|
| 193 |
+
|
| 194 |
+
f"**Step 6:** Substitute values.\n"
|
| 195 |
+
f"t = (1/{k}) × ln({C_A0}/{C_A})\n"
|
| 196 |
+
f"t = {round(1/k, 2)} × ln({round(C_A0/C_A, 3)})\n"
|
| 197 |
+
f"t = {round(1/k, 2)} × {round(math.log(C_A0/C_A), 3)}\n"
|
| 198 |
+
f"t = {round(time, 2)} s\n\n"
|
| 199 |
+
|
| 200 |
+
f"**Answer:** The required reaction time is {round(time, 2)} seconds."
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
return question, solution
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
# Template 4 (Advanced)
|
| 207 |
+
def template_batch_reactor_second_order():
|
| 208 |
+
"""
|
| 209 |
+
Batch Reactor Time Calculation - Second Order Kinetics
|
| 210 |
+
|
| 211 |
+
Scenario:
|
| 212 |
+
This template calculates the reaction time in a constant-volume batch
|
| 213 |
+
reactor for a second-order reaction. In this case, the reaction rate
|
| 214 |
+
depends on the square of the reactant's concentration (-r_A = k*C_A²).
|
| 215 |
+
The time required is determined by integrating the mole balance
|
| 216 |
+
equation for these kinetics. Given the initial and final concentrations
|
| 217 |
+
and the second-order rate constant, the goal is to compute the time using:
|
| 218 |
+
|
| 219 |
+
t = (1/k) * (1/C_A - 1/C_A0)
|
| 220 |
+
|
| 221 |
+
Returns:
|
| 222 |
+
tuple: A tuple containing:
|
| 223 |
+
- str: A question asking to calculate the required reaction time.
|
| 224 |
+
- str: A step-by-step solution showing the derivation and calculation.
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
reactant_name = random.choice(LIQUID_PHASE_REACTANTS)
|
| 228 |
+
C_A0 = round(random.uniform(0.5, 2.0), 2) # Lower values for second-order
|
| 229 |
+
|
| 230 |
+
conversion = round(random.uniform(0.4, 0.85), 2)
|
| 231 |
+
C_A = round(C_A0 * (1 - conversion), 2)
|
| 232 |
+
|
| 233 |
+
# Second-order rate constant (L/(mol·s))
|
| 234 |
+
k = round(random.uniform(0.1, 1.0), 3)
|
| 235 |
+
|
| 236 |
+
# Calculate time
|
| 237 |
+
time = (1/C_A - 1/C_A0) / k
|
| 238 |
+
|
| 239 |
+
question = (
|
| 240 |
+
f"A second-order reaction of {reactant_name} takes place in a batch reactor. "
|
| 241 |
+
f"Starting with {C_A0} mol/L, the concentration drops to {C_A} mol/L. "
|
| 242 |
+
f"Given that the second-order rate constant is {k} L/(mol·s), "
|
| 243 |
+
f"calculate the time required for this conversion."
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
solution = (
|
| 247 |
+
f"**Given:**\n"
|
| 248 |
+
f"- Initial concentration (C_A0) = {C_A0} mol/L\n"
|
| 249 |
+
f"- Final concentration (C_A) = {C_A} mol/L\n"
|
| 250 |
+
f"- Second-order rate constant (k) = {k} L/(mol·s)\n"
|
| 251 |
+
f"- Conversion = {round(conversion * 100, 1)}%\n\n"
|
| 252 |
+
|
| 253 |
+
f"**Step 1:** Write the rate law for second-order kinetics.\n"
|
| 254 |
+
f"-r_A = k × C_A²\n\n"
|
| 255 |
+
|
| 256 |
+
f"**Step 2:** Set up the mole balance equation.\n"
|
| 257 |
+
f"dC_A/dt = -k × C_A²\n\n"
|
| 258 |
+
|
| 259 |
+
f"**Step 3:** Separate variables and integrate.\n"
|
| 260 |
+
f"dC_A/C_A² = -k × dt\n"
|
| 261 |
+
f"∫[C_A0 to C_A] C_A⁻² dC_A = -k ∫[0 to t] dt\n\n"
|
| 262 |
+
|
| 263 |
+
f"**Step 4:** Solve the integral.\n"
|
| 264 |
+
f"[-1/C_A] from C_A0 to C_A = -k × t\n"
|
| 265 |
+
f"-1/C_A + 1/C_A0 = -k × t\n"
|
| 266 |
+
f"1/C_A - 1/C_A0 = k × t\n\n"
|
| 267 |
+
|
| 268 |
+
f"**Step 5:** Solve for time.\n"
|
| 269 |
+
f"t = (1/k) × (1/C_A - 1/C_A0)\n\n"
|
| 270 |
+
|
| 271 |
+
f"**Step 6:** Substitute values.\n"
|
| 272 |
+
f"t = (1/{k}) × (1/{C_A} - 1/{C_A0})\n"
|
| 273 |
+
f"t = {round(1/k, 3)} × ({round(1/C_A, 3)} - {round(1/C_A0, 3)})\n"
|
| 274 |
+
f"t = {round(1/k, 3)} × {round(1/C_A - 1/C_A0, 3)}\n"
|
| 275 |
+
f"t = {round(time, 2)} s\n\n"
|
| 276 |
+
|
| 277 |
+
f"**Answer:** The required reaction time is {round(time, 2)} seconds."
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
return question, solution
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
# Template 5 (Advanced)
|
| 284 |
+
def template_pfr_volume_changing_rate():
|
| 285 |
+
"""
|
| 286 |
+
PFR Volume Calculation - Arbitrary Order Kinetics
|
| 287 |
+
|
| 288 |
+
Scenario:
|
| 289 |
+
This template calculates the required volume for a Plug Flow Reactor (PFR)
|
| 290 |
+
to achieve a target conversion for a liquid-phase (constant-density)
|
| 291 |
+
reaction. The reaction follows an arbitrary, non-integer order, where the
|
| 292 |
+
rate law is -r_A = k * C_A^n. Because an analytical solution is often
|
| 293 |
+
complex for such cases, the PFR design equation is solved using
|
| 294 |
+
numerical integration:
|
| 295 |
+
|
| 296 |
+
V = ∫[0 to X] (F_A0 / (-r_A)) dX
|
| 297 |
+
|
| 298 |
+
Returns:
|
| 299 |
+
tuple: A tuple containing:
|
| 300 |
+
- str: A question asking to calculate the required reactor volume.
|
| 301 |
+
- str: A step-by-step solution showing the setup and numerical result.
|
| 302 |
+
"""
|
| 303 |
+
|
| 304 |
+
reactant_name = random.choice(LIQUID_PHASE_REACTANTS)
|
| 305 |
+
F_A0 = round(random.uniform(1.0, 4.0), 2)
|
| 306 |
+
X_final = round(random.uniform(0.5, 0.85), 2)
|
| 307 |
+
|
| 308 |
+
# Reaction order (non-integer for complexity)
|
| 309 |
+
n = round(random.uniform(1.5, 2.5), 1)
|
| 310 |
+
|
| 311 |
+
# Rate constant and initial concentration
|
| 312 |
+
k = round(random.uniform(0.05, 0.5), 3)
|
| 313 |
+
C_A0 = round(random.uniform(0.5, 2.0), 2) # mol/L
|
| 314 |
+
|
| 315 |
+
def rate_function_advanced(X):
|
| 316 |
+
"""Rate as function of conversion: -r_A = k * C_A0^n * (1-X)^n"""
|
| 317 |
+
return k * (C_A0**n) * ((1 - X)**n)
|
| 318 |
+
|
| 319 |
+
def integrand_advanced(X):
|
| 320 |
+
"""Integrand for PFR design equation"""
|
| 321 |
+
return F_A0 / rate_function_advanced(X)
|
| 322 |
+
|
| 323 |
+
# Numerical integration (analytical solution complex for arbitrary n)
|
| 324 |
+
volume, integration_error = quad(integrand_advanced, 0, X_final)
|
| 325 |
+
|
| 326 |
+
question = (
|
| 327 |
+
f"An {n}-order liquid-phase reaction of {reactant_name} (A → products) occurs in a PFR. "
|
| 328 |
+
f"The inlet conditions are: F_A0 = {F_A0} mol/s and C_A0 = {C_A0} mol/L. "
|
| 329 |
+
f"The rate expression is: -r_A = {k} × C_A^{n} mol/(L·s). "
|
| 330 |
+
f"Determine the reactor volume needed for {X_final*100}% conversion."
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
solution = (
|
| 334 |
+
f"**Given:**\n"
|
| 335 |
+
f"- Inlet molar flow rate: F_A0 = {F_A0} mol/s\n"
|
| 336 |
+
f"- Initial concentration: C_A0 = {C_A0} mol/L\n"
|
| 337 |
+
f"- Rate constant: k = {k} L^{n-1}/(mol^{n-1}·s)\n"
|
| 338 |
+
f"- Reaction order: n = {n}\n"
|
| 339 |
+
f"- Desired conversion: X = {X_final}\n\n"
|
| 340 |
+
|
| 341 |
+
f"**Step 1:** Express rate in terms of conversion.\n"
|
| 342 |
+
f"C_A = C_A0(1 - X)\n"
|
| 343 |
+
f"-r_A = k × C_A^{n} = k × C_A0^{n} × (1 - X)^{n}\n"
|
| 344 |
+
f"-r_A = {k} × {C_A0}^{n} × (1 - X)^{n}\n"
|
| 345 |
+
f"-r_A = {round(k * (C_A0**n), 4)} × (1 - X)^{n}\n\n"
|
| 346 |
+
|
| 347 |
+
f"**Step 2:** Set up the PFR integral.\n"
|
| 348 |
+
f"V = ∫[0 to {X_final}] (F_A0 / (-r_A)) dX\n"
|
| 349 |
+
f"V = ∫[0 to {X_final}] ({F_A0} / ({round(k * (C_A0**n), 4)} × (1 - X)^{n})) dX\n\n"
|
| 350 |
+
|
| 351 |
+
f"**Step 3:** This integral requires numerical methods for n = {n}.\n"
|
| 352 |
+
f"Using numerical integration (scipy.integrate.quad):\n\n"
|
| 353 |
+
|
| 354 |
+
f"**Step 4:** Numerical result.\n"
|
| 355 |
+
f"V = {round(volume, 2)} L\n"
|
| 356 |
+
f"Integration error: {integration_error:.2e}\n\n"
|
| 357 |
+
|
| 358 |
+
f"**Note:** For non-integer reaction orders, analytical solutions are complex.\n"
|
| 359 |
+
f"Numerical integration is the standard approach in reactor design.\n\n"
|
| 360 |
+
|
| 361 |
+
f"**Answer:** The required PFR volume is {round(volume, 2)} liters."
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
return question, solution
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
def main():
|
| 368 |
+
"""
|
| 369 |
+
Generate numerous instances of each mole balances template with different random seeds
|
| 370 |
+
and write the results to a JSONL file.
|
| 371 |
+
"""
|
| 372 |
+
import json
|
| 373 |
+
import os
|
| 374 |
+
|
| 375 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 376 |
+
output_file = "testset/chemical_engineering/reaction_kinetics/mole_balances.jsonl"
|
| 377 |
+
|
| 378 |
+
# Create the directory if it doesn't exist
|
| 379 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 380 |
+
|
| 381 |
+
# List of template functions with their ID and level
|
| 382 |
+
templates = [
|
| 383 |
+
(template_cstr_volume_basic, "cstr_volume_basic", "Easy"),
|
| 384 |
+
(template_batch_reactor_zero_order, "batch_reactor_zero_order", "Easy"),
|
| 385 |
+
(template_batch_reactor_first_order, "batch_reactor_first_order", "Intermediate"),
|
| 386 |
+
(template_batch_reactor_second_order, "batch_reactor_second_order", "Advanced"),
|
| 387 |
+
(template_pfr_volume_changing_rate, "pfr_volume_changing_rate", "Advanced"),
|
| 388 |
+
]
|
| 389 |
+
|
| 390 |
+
# List to store all generated problems
|
| 391 |
+
all_problems = []
|
| 392 |
+
|
| 393 |
+
# Generate problems for each template
|
| 394 |
+
for template_func, id_name, level in templates:
|
| 395 |
+
for _ in range(50):
|
| 396 |
+
# Generate a unique seed for each problem
|
| 397 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 398 |
+
random.seed(seed)
|
| 399 |
+
|
| 400 |
+
# Generate the problem and solution
|
| 401 |
+
question, solution = template_func()
|
| 402 |
+
|
| 403 |
+
# Create a JSON entry
|
| 404 |
+
problem_entry = {
|
| 405 |
+
"seed": seed,
|
| 406 |
+
"branch": "chemical_engineering",
|
| 407 |
+
"domain": "reaction_kinetics",
|
| 408 |
+
"area": "mole_balances",
|
| 409 |
+
"id": id_name,
|
| 410 |
+
"level": level,
|
| 411 |
+
"question": question,
|
| 412 |
+
"solution": solution
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
# Add to the list of problems
|
| 416 |
+
all_problems.append(problem_entry)
|
| 417 |
+
|
| 418 |
+
# Shuffle the problems to mix templates and levels
|
| 419 |
+
random.shuffle(all_problems)
|
| 420 |
+
|
| 421 |
+
# Write all problems to a .jsonl file
|
| 422 |
+
with open(output_file, "w") as file:
|
| 423 |
+
for problem in all_problems:
|
| 424 |
+
file.write(json.dumps(problem))
|
| 425 |
+
file.write("\n")
|
| 426 |
+
|
| 427 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
if __name__ == "__main__":
|
| 431 |
+
main()
|
data/templates/branches/chemical_engineering/reaction_kinetics/stoichiometry.py
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
from data.templates.branches.chemical_engineering.constants import GENERAL_REACTANTS, LIQUID_PHASE_REACTANTS, GAS_PHASE_REACTANTS, PRODUCTS
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_batch_moles_vs_conversion():
|
| 7 |
+
"""
|
| 8 |
+
Batch System Moles vs. Conversion
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template tests the fundamental relationship between conversion ($X_A$)
|
| 12 |
+
and the number of moles of each species in a batch reactor. For a given
|
| 13 |
+
reaction like $aA + bB -> cC + dD$, the goal is to calculate the final
|
| 14 |
+
number of moles of all species based on the initial moles and a specific
|
| 15 |
+
conversion of the limiting reactant using the core stoichiometric relations:
|
| 16 |
+
|
| 17 |
+
$N_A = N_{A0}(1 - X_A)$
|
| 18 |
+
$N_B = N_{B0} - (b/a)N_{A0}X_A$
|
| 19 |
+
$N_C = N_{C0} + (c/a)N_{A0}X_A$
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
tuple: A tuple containing:
|
| 23 |
+
- str: A question about calculating final moles in a batch reactor.
|
| 24 |
+
- str: A detailed, step-by-step solution.
|
| 25 |
+
"""
|
| 26 |
+
# 1. Randomized Parameters
|
| 27 |
+
|
| 28 |
+
# Select names for reactants and products
|
| 29 |
+
reactant_A_name, reactant_B_name = random.sample(LIQUID_PHASE_REACTANTS, 2)
|
| 30 |
+
product_C_name, product_D_name = random.sample(PRODUCTS, 2)
|
| 31 |
+
|
| 32 |
+
# Generate stoichiometric coefficients
|
| 33 |
+
a = random.choice([1, 2])
|
| 34 |
+
b = random.randint(1, 3)
|
| 35 |
+
c = random.randint(1, 3)
|
| 36 |
+
d = random.randint(1, 3)
|
| 37 |
+
|
| 38 |
+
# Generate initial moles, ensuring A is the limiting reactant
|
| 39 |
+
N_A0 = round(random.uniform(10.0, 25.0), 2)
|
| 40 |
+
# Ensure B is in excess (at least 20% more than stoichiometrically required)
|
| 41 |
+
N_B0 = round((N_A0 * b / a) * random.uniform(1.2, 2.0), 2)
|
| 42 |
+
# Initial moles of products are often zero but can be non-zero
|
| 43 |
+
N_C0 = round(random.choice([0.0, random.uniform(1.0, 5.0)]), 2)
|
| 44 |
+
N_D0 = round(random.choice([0.0, random.uniform(1.0, 5.0)]), 2)
|
| 45 |
+
|
| 46 |
+
# Generate a realistic conversion for the limiting reactant A
|
| 47 |
+
X_A = round(random.uniform(0.40, 0.95), 2)
|
| 48 |
+
|
| 49 |
+
# 2. Core Calculations
|
| 50 |
+
N_A = N_A0 * (1 - X_A)
|
| 51 |
+
N_B = N_B0 - (b / a) * N_A0 * X_A
|
| 52 |
+
N_C = N_C0 + (c / a) * N_A0 * X_A
|
| 53 |
+
N_D = N_D0 + (d / a) * N_A0 * X_A
|
| 54 |
+
|
| 55 |
+
# 3. Generate Question and Solution Strings
|
| 56 |
+
|
| 57 |
+
# Helper function to format the reaction string cleanly (e.g., "A" instead of "1A")
|
| 58 |
+
def format_species(coeff, name):
|
| 59 |
+
return f"{coeff if coeff > 1 else ''}{name}"
|
| 60 |
+
|
| 61 |
+
reaction_string = (
|
| 62 |
+
f"{format_species(a, reactant_A_name)} + {format_species(b, reactant_B_name)} -> "
|
| 63 |
+
f"{format_species(c, product_C_name)} + {format_species(d, product_D_name)}"
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
question = (
|
| 67 |
+
f"Consider the following elementary liquid-phase reaction carried out in a batch reactor:\n"
|
| 68 |
+
f"**Reaction:** ${reaction_string}$\n\n"
|
| 69 |
+
f"The reactor is initially charged with ${N_A0}$ moles of {reactant_A_name}, "
|
| 70 |
+
f"${N_B0}$ moles of {reactant_B_name}, and ${N_C0}$ moles of {product_C_name}. "
|
| 71 |
+
f"{reactant_A_name} is the limiting reactant.\n\n"
|
| 72 |
+
f"If the reaction is allowed to proceed until a conversion of ${X_A}$ ($_A$) is achieved, "
|
| 73 |
+
f"calculate the final number of moles of all species in the reactor."
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
solution = (
|
| 77 |
+
f"**Step 1:** Identify Given Information\n"
|
| 78 |
+
f"- Reaction: ${reaction_string}$\n"
|
| 79 |
+
f"- Initial Moles:\n"
|
| 80 |
+
f" - $N_{{{reactant_A_name},0}} = {N_A0}$ mol\n"
|
| 81 |
+
f" - $N_{{{reactant_B_name},0}} = {N_B0}$ mol\n"
|
| 82 |
+
f" - $N_{{{product_C_name},0}} = {N_C0}$ mol\n"
|
| 83 |
+
f" - $N_{{{product_D_name},0}} = {N_D0}$ mol\n"
|
| 84 |
+
f"- Conversion of {reactant_A_name}: $X_A = {X_A}$\n\n"
|
| 85 |
+
|
| 86 |
+
f"**Step 2:** Write Stoichiometric Relations in Terms of Conversion\n"
|
| 87 |
+
f"The number of moles of each species ($N_j$) at any conversion $X_A$ can be expressed using the following design equations for a batch system:\n"
|
| 88 |
+
f"- $N_A = N_{{A,0}}(1 - X_A)$\n"
|
| 89 |
+
f"- $N_B = N_{{B,0}} - (b/a) N_{{A,0}} X_A$\n"
|
| 90 |
+
f"- $N_C = N_{{C,0}} + (c/a) N_{{A,0}} X_A$\n"
|
| 91 |
+
f"- $N_D = N_{{D,0}} + (d/a) N_{{A,0}} X_A$\n\n"
|
| 92 |
+
|
| 93 |
+
f"**Step 3:** Calculate Final Moles for Each Species\n"
|
| 94 |
+
f"For {reactant_A_name} (A):\n"
|
| 95 |
+
f"$N_A = {N_A0}(1 - {X_A}) = {N_A0}({1-X_A}) = {round(N_A, 3)}$ mol\n\n"
|
| 96 |
+
f"For {reactant_B_name} (B):\n"
|
| 97 |
+
f"$N_B = {N_B0} - ({b}/{a}) \\times {N_A0} \\times {X_A} = {N_B0} - {round((b/a)*N_A0*X_A, 3)} = {round(N_B, 3)}$ mol\n\n"
|
| 98 |
+
f"For {product_C_name} (C):\n"
|
| 99 |
+
f"$N_C = {N_C0} + ({c}/{a}) \\times {N_A0} \\times {X_A} = {N_C0} + {round((c/a)*N_A0*X_A, 3)} = {round(N_C, 3)}$ mol\n\n"
|
| 100 |
+
f"For {product_D_name} (D):\n"
|
| 101 |
+
f"$N_D = {N_D0} + ({d}/{a}) \\times {N_A0} \\times {X_A} = {N_D0} + {round((d/a)*N_A0*X_A, 3)} = {round(N_D, 3)}$ mol\n\n"
|
| 102 |
+
|
| 103 |
+
f"**Final Answer**\n"
|
| 104 |
+
f"After reaching a conversion of ${X_A*100} \%$, the final number of moles in the reactor are:\n"
|
| 105 |
+
f"- {reactant_A_name}: ${round(N_A, 3)}$ mol\n"
|
| 106 |
+
f"- {reactant_B_name}: ${round(N_B, 3)}$ mol\n"
|
| 107 |
+
f"- {product_C_name}: ${round(N_C, 3)}$ mol\n"
|
| 108 |
+
f"- {product_D_name}: ${round(N_D, 3)}$ mol"
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
return question, solution
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
# Template 2 (Intermediate)
|
| 115 |
+
def template_flow_system_molar_flow_rates():
|
| 116 |
+
"""
|
| 117 |
+
Flow System Molar Flow Rates vs. Conversion
|
| 118 |
+
|
| 119 |
+
Scenario:
|
| 120 |
+
This template tests the ability to determine the outlet molar flow rates
|
| 121 |
+
of all species in a continuous flow reactor (e.g., CSTR, PFR) given the
|
| 122 |
+
inlet flow rates and the conversion of a limiting reactant. The calculation
|
| 123 |
+
is an application of stoichiometric principles to a flow system, using:
|
| 124 |
+
|
| 125 |
+
$F_A = F_{A0}(1 - X_A)$
|
| 126 |
+
$F_j = F_{j0} + \\nu_j F_{A0} X_A = F_{A0}(\\Theta_j + \\nu_j X_A)$
|
| 127 |
+
|
| 128 |
+
where $\\nu_j$ is the stoichiometric coefficient and $\\Theta_j = F_{j0}/F_{A0}$.
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
tuple: A tuple containing:
|
| 132 |
+
- str: A question about calculating outlet molar flow rates.
|
| 133 |
+
- str: A detailed, step-by-step solution.
|
| 134 |
+
"""
|
| 135 |
+
# 1. Randomized Parameters
|
| 136 |
+
|
| 137 |
+
# Select unique names for reactants and products
|
| 138 |
+
reactant_A_name, reactant_B_name = random.sample(GENERAL_REACTANTS, 2)
|
| 139 |
+
product_C_name, product_D_name = random.sample(PRODUCTS, 2)
|
| 140 |
+
|
| 141 |
+
# Generate stoichiometric coefficients
|
| 142 |
+
a = random.choice([1, 2])
|
| 143 |
+
b = random.randint(1, 3)
|
| 144 |
+
c = random.randint(1, 3)
|
| 145 |
+
d = random.randint(1, 3)
|
| 146 |
+
|
| 147 |
+
# Generate inlet molar flow rates (mol/min), ensuring A is limiting
|
| 148 |
+
F_A0 = round(random.uniform(50.0, 150.0), 2)
|
| 149 |
+
F_B0 = round((F_A0 * b / a) * random.uniform(1.2, 2.5), 2)
|
| 150 |
+
F_C0 = round(random.choice([0.0, random.uniform(5.0, 20.0)]), 2)
|
| 151 |
+
F_D0 = 0.0
|
| 152 |
+
|
| 153 |
+
# Generate a realistic conversion
|
| 154 |
+
X_A = round(random.uniform(0.50, 0.90), 2)
|
| 155 |
+
|
| 156 |
+
# 2. Core Calculations
|
| 157 |
+
|
| 158 |
+
# Calculate Theta values for use in the solution string
|
| 159 |
+
Theta_B = F_B0 / F_A0
|
| 160 |
+
Theta_C = F_C0 / F_A0
|
| 161 |
+
|
| 162 |
+
# Calculate outlet molar flow rates
|
| 163 |
+
F_A = F_A0 * (1 - X_A)
|
| 164 |
+
F_B = F_B0 - (b / a) * F_A0 * X_A
|
| 165 |
+
F_C = F_C0 + (c / a) * F_A0 * X_A
|
| 166 |
+
F_D = F_D0 + (d / a) * F_A0 * X_A
|
| 167 |
+
|
| 168 |
+
# 3. Generate Question and Solution Strings
|
| 169 |
+
|
| 170 |
+
def format_species(coeff, name):
|
| 171 |
+
return f"{coeff if coeff > 1 else ''}{' ' if coeff > 1 else ''}{name}"
|
| 172 |
+
|
| 173 |
+
reaction_string = (
|
| 174 |
+
f"{format_species(a, reactant_A_name)} + {format_species(b, reactant_B_name)} -> "
|
| 175 |
+
f"{format_species(c, product_C_name)} + {format_species(d, product_D_name)}"
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
question = (
|
| 179 |
+
f"A reaction is carried out in a steady-state Plug Flow Reactor (PFR):\n"
|
| 180 |
+
f"**Reaction:** ${reaction_string}$\n\n"
|
| 181 |
+
f"The reactor is fed with {reactant_A_name} at a rate of ${F_A0}$ mol/min, "
|
| 182 |
+
f"{reactant_B_name} at ${F_B0}$ mol/min, and {product_C_name} at ${F_C0}$ mol/min. "
|
| 183 |
+
f"{reactant_A_name} is the limiting reactant.\n\n"
|
| 184 |
+
f"The reactor is designed to achieve a conversion of ${X_A}$ ($X_A$) for the limiting reactant. "
|
| 185 |
+
f"Calculate the molar flow rates of all species exiting the reactor."
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
solution = (
|
| 189 |
+
f"**Step 1:** Identify Given Information\n"
|
| 190 |
+
f"- Reaction: ${reaction_string}$\n"
|
| 191 |
+
f"- Inlet Molar Flow Rates:\n"
|
| 192 |
+
f" - $F_{{A,0}} = {F_A0}$ mol/min\n"
|
| 193 |
+
f" - $F_{{B,0}} = {F_B0}$ mol/min\n"
|
| 194 |
+
f" - $F_{{C,0}} = {F_C0}$ mol/min\n"
|
| 195 |
+
f" - $F_{{D,0}} = {F_D0}$ mol/min\n"
|
| 196 |
+
f"- Conversion of {reactant_A_name}: $X_A = {X_A}$\n\n"
|
| 197 |
+
|
| 198 |
+
f"**Step 2:** Stoichiometric Relations for a Flow System\n"
|
| 199 |
+
f"The outlet molar flow rate ($F_j$) of any species is given by:\n"
|
| 200 |
+
f"$F_j = F_{{j,0}} + \\nu_j F_{{A,0}} X_A$\n"
|
| 201 |
+
f"Alternatively, in terms of $\\Theta_j = F_{{j,0}} / F_{{A,0}}$:\n"
|
| 202 |
+
f"$F_j = F_{{A,0}}(\\Theta_j + \\nu_j X_A)$\n"
|
| 203 |
+
f"Here, the normalized stoichiometric coefficients ($\\nu_j$) are:\n"
|
| 204 |
+
f"$\\nu_A = -1$, $\\nu_B = -{b}/{a}$, $\\nu_C = +{c}/{a}$, $\\nu_D = +{d}/{a}$\n\n"
|
| 205 |
+
|
| 206 |
+
f"**Step 3:** Calculate Outlet Molar Flow Rates\n\n"
|
| 207 |
+
f"**For {reactant_A_name} (A):**\n"
|
| 208 |
+
f"$F_A = F_{{A,0}}(1 - X_A) = {F_A0}(1 - {X_A}) = {round(F_A, 2)}$ mol/min\n\n"
|
| 209 |
+
|
| 210 |
+
f"**For {reactant_B_name} (B):**\n"
|
| 211 |
+
f"*Using the Direct Method:*\n"
|
| 212 |
+
f"$F_B = F_{{B,0}} - ({b}/{a}) F_{{A,0}} X_A = {F_B0} - ({b}/{a}) \\times {F_A0} \\times {X_A} = {round(F_B, 2)}$ mol/min\n"
|
| 213 |
+
f"*Using the $\\Theta$ Method:*\n"
|
| 214 |
+
f"First, $\\Theta_B = F_{{B,0}} / F_{{A,0}} = {F_B0} / {F_A0} = {round(Theta_B, 3)}$\n"
|
| 215 |
+
f"$F_B = F_{{A,0}}(\\Theta_B - ({b}/{a})X_A) = {F_A0}({round(Theta_B, 3)} - ({b}/{a}) \\times {X_A}) = {round(F_B, 2)}$ mol/min\n\n"
|
| 216 |
+
|
| 217 |
+
f"**For {product_C_name} (C):**\n"
|
| 218 |
+
f"*Using the Direct Method:*\n"
|
| 219 |
+
f"$F_C = F_{{C,0}} + ({c}/{a}) F_{{A,0}} X_A = {F_C0} + ({c}/{a}) \\times {F_A0} \\times {X_A} = {round(F_C, 2)}$ mol/min\n"
|
| 220 |
+
f"*Using the $\\Theta$ Method:*\n"
|
| 221 |
+
f"First, $\\Theta_C = F_{{C,0}} / F_{{A,0}} = {F_C0} / {F_A0} = {round(Theta_C, 3)}$\n"
|
| 222 |
+
f"$F_C = F_{{A,0}}(\\Theta_C + ({c}/{a})X_A) = {F_A0}({round(Theta_C, 3)} + ({c}/{a}) \\times {X_A}) = {round(F_C, 2)}$ mol/min\n\n"
|
| 223 |
+
|
| 224 |
+
f"**For {product_D_name} (D):**\n"
|
| 225 |
+
f"Since $F_{{D,0}} = 0$, $\\Theta_D = 0$. The calculation is straightforward:\n"
|
| 226 |
+
f"$F_D = F_{{D,0}} + ({d}/{a}) F_{{A,0}} X_A = 0 + ({d}/{a}) \\times {F_A0} \\times {X_A} = {round(F_D, 2)}$ mol/min\n\n"
|
| 227 |
+
|
| 228 |
+
f"**Final Answer**\n"
|
| 229 |
+
f"The molar flow rates exiting the reactor are:\n"
|
| 230 |
+
f"- {reactant_A_name} ($F_A$): ${round(F_A, 2)}$ mol/min\n"
|
| 231 |
+
f"- {reactant_B_name} ($F_B$): ${round(F_B, 2)}$ mol/min\n"
|
| 232 |
+
f"- {product_C_name} ($F_C$): ${round(F_C, 2)}$ mol/min\n"
|
| 233 |
+
f"- {product_D_name} ($F_D$): ${round(F_D, 2)}$ mol/min"
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
return question, solution
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
# Template 3 (Intermediate)
|
| 240 |
+
def template_limiting_reactant():
|
| 241 |
+
"""
|
| 242 |
+
Finding and Using the Limiting Reactant
|
| 243 |
+
|
| 244 |
+
Scenario:
|
| 245 |
+
This template adds a crucial preliminary step to stoichiometric calculations.
|
| 246 |
+
Given a reaction and the initial moles of multiple reactants, the user must
|
| 247 |
+
first identify the limiting reactant by comparing the ratio of initial moles
|
| 248 |
+
to the stoichiometric coefficient for each species:
|
| 249 |
+
|
| 250 |
+
Compare: $N_{A0}/a$ vs. $N_{B0}/b$
|
| 251 |
+
|
| 252 |
+
The species with the smaller ratio is limiting. Then, using a given
|
| 253 |
+
conversion with respect to that limiting reactant, the user calculates the
|
| 254 |
+
final moles of all species.
|
| 255 |
+
|
| 256 |
+
Returns:
|
| 257 |
+
tuple: A tuple containing:
|
| 258 |
+
- str: A question requiring identification of the limiting reactant.
|
| 259 |
+
- str: A detailed solution showing the identification and calculation steps.
|
| 260 |
+
"""
|
| 261 |
+
# 1. Randomized Parameters
|
| 262 |
+
|
| 263 |
+
# Select unique names for reactants and products
|
| 264 |
+
reactant_A_name, reactant_B_name = random.sample(GENERAL_REACTANTS, 2)
|
| 265 |
+
product_C_name, product_D_name = random.sample(PRODUCTS, 2)
|
| 266 |
+
|
| 267 |
+
# Generate stoichiometric coefficients
|
| 268 |
+
a = random.randint(1, 3)
|
| 269 |
+
b = random.randint(1, 3)
|
| 270 |
+
c = random.randint(1, 3)
|
| 271 |
+
d = random.randint(1, 3)
|
| 272 |
+
|
| 273 |
+
# Randomly decide which reactant will be limiting
|
| 274 |
+
is_A_limiting = random.choice([True, False])
|
| 275 |
+
|
| 276 |
+
# Generate initial moles based on which reactant is limiting
|
| 277 |
+
if is_A_limiting:
|
| 278 |
+
N_A0 = round(random.uniform(20.0, 50.0), 2)
|
| 279 |
+
# Make B the excess reactant
|
| 280 |
+
N_B0 = round((N_A0 * b / a) * random.uniform(1.1, 2.0), 2)
|
| 281 |
+
else: # B is limiting
|
| 282 |
+
N_B0 = round(random.uniform(20.0, 50.0), 2)
|
| 283 |
+
# Make A the excess reactant
|
| 284 |
+
N_A0 = round((N_B0 * a / b) * random.uniform(1.1, 2.0), 2)
|
| 285 |
+
|
| 286 |
+
N_C0 = 0.0
|
| 287 |
+
N_D0 = 0.0
|
| 288 |
+
|
| 289 |
+
# Generate a realistic conversion for the limiting reactant
|
| 290 |
+
X = round(random.uniform(0.60, 0.95), 2)
|
| 291 |
+
|
| 292 |
+
# 2. Core Calculations & Logic
|
| 293 |
+
|
| 294 |
+
# Determine the limiting reactant
|
| 295 |
+
ratio_A = N_A0 / a
|
| 296 |
+
ratio_B = N_B0 / b
|
| 297 |
+
|
| 298 |
+
if ratio_A <= ratio_B:
|
| 299 |
+
# Case 1: A is the limiting reactant
|
| 300 |
+
limiting_reactant_name = reactant_A_name
|
| 301 |
+
limiting_reactant_sym = 'A'
|
| 302 |
+
excess_reactant_name = reactant_B_name
|
| 303 |
+
|
| 304 |
+
# Calculate final moles based on X_A
|
| 305 |
+
N_A = N_A0 * (1 - X)
|
| 306 |
+
N_B = N_B0 - (b / a) * N_A0 * X
|
| 307 |
+
N_C = N_C0 + (c / a) * N_A0 * X
|
| 308 |
+
N_D = N_D0 + (d / a) * N_A0 * X
|
| 309 |
+
else:
|
| 310 |
+
# Case 2: B is the limiting reactant
|
| 311 |
+
limiting_reactant_name = reactant_B_name
|
| 312 |
+
limiting_reactant_sym = 'B'
|
| 313 |
+
excess_reactant_name = reactant_A_name
|
| 314 |
+
|
| 315 |
+
# Calculate final moles based on X_B
|
| 316 |
+
N_A = N_A0 - (a / b) * N_B0 * X
|
| 317 |
+
N_B = N_B0 * (1 - X)
|
| 318 |
+
N_C = N_C0 + (c / b) * N_B0 * X
|
| 319 |
+
N_D = N_D0 + (d / b) * N_B0 * X
|
| 320 |
+
|
| 321 |
+
# 3. Generate Question and Solution Strings
|
| 322 |
+
def format_species(coeff, name):
|
| 323 |
+
return f"{coeff if coeff > 1 else ''}{' ' if coeff > 1 else ''}{name}"
|
| 324 |
+
|
| 325 |
+
reaction_string = (
|
| 326 |
+
f"{format_species(a, reactant_A_name)} + {format_species(b, reactant_B_name)} -> "
|
| 327 |
+
f"{format_species(c, product_C_name)} + {format_species(d, product_D_name)}"
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
question = (
|
| 331 |
+
f"The following reaction occurs in a batch reactor:\n"
|
| 332 |
+
f"**Reaction:** ${reaction_string}$\n\n"
|
| 333 |
+
f"The reactor is initially charged with ${N_A0}$ moles of {reactant_A_name} and "
|
| 334 |
+
f"${N_B0}$ moles of {reactant_B_name}. The reaction is allowed to proceed until "
|
| 335 |
+
f"a ${X*100}\%$ conversion of the limiting reactant is achieved.\n\n"
|
| 336 |
+
f"First, identify the limiting reactant. Then, calculate the final number of moles for all species."
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
solution_step1_identification = (
|
| 340 |
+
f"**Step 1:** Identify the Limiting Reactant\n"
|
| 341 |
+
f"To find the limiting reactant, we compare the ratio of the initial moles to the "
|
| 342 |
+
f"stoichiometric coefficient for each reactant.\n\n"
|
| 343 |
+
f"- For **{reactant_A_name} (A)**: $N_{{A0}}/a = {N_A0} / {a} = \\mathbf{{{round(ratio_A, 2)}}}$\n"
|
| 344 |
+
f"- For **{reactant_B_name} (B)**: $N_{{B0}}/b = {N_B0} / {b} = \\mathbf{{{round(ratio_B, 2)}}}$\n\n"
|
| 345 |
+
f"Since ${round(min(ratio_A, ratio_B), 2)} < {round(max(ratio_A, ratio_B), 2)}$, "
|
| 346 |
+
f"**{limiting_reactant_name} is the limiting reactant**. All further calculations will be based on "
|
| 347 |
+
f"a conversion of {limiting_reactant_name}, $X_{limiting_reactant_sym} = {X}$.\n\n"
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
solution_step2_calculation = (
|
| 351 |
+
f"**Step 2:** Calculate Final Moles\n"
|
| 352 |
+
f"Using the stoichiometric relationships based on the limiting reactant ({limiting_reactant_name}):\n\n"
|
| 353 |
+
f"**For {reactant_A_name} (A):**\n"
|
| 354 |
+
f"$N_A = {round(N_A, 2)}$ mol\n\n"
|
| 355 |
+
f"**For {reactant_B_name} (B):**\n"
|
| 356 |
+
f"$N_B = {round(N_B, 2)}$ mol\n\n"
|
| 357 |
+
f"**For {product_C_name} (C):**\n"
|
| 358 |
+
f"$N_C = {round(N_C, 2)}$ mol\n\n"
|
| 359 |
+
f"**For {product_D_name} (D):**\n"
|
| 360 |
+
f"$N_D = {round(N_D, 2)}$ mol"
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
solution_final_answer = (
|
| 364 |
+
f"**Final Answer**\n"
|
| 365 |
+
f"The limiting reactant is **{limiting_reactant_name}**. The final number of moles are:\n"
|
| 366 |
+
f"- **{reactant_A_name}:** ${round(N_A, 2)}$ mol\n"
|
| 367 |
+
f"- **{reactant_B_name}:** ${round(N_B, 2)}$ mol\n"
|
| 368 |
+
f"- **{product_C_name}:** ${round(N_C, 2)}$ mol\n"
|
| 369 |
+
f"- **{product_D_name}:** ${round(N_D, 2)}$ mol"
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
if limiting_reactant_sym == 'A':
|
| 373 |
+
solution_step2_calculation = (
|
| 374 |
+
f"\n\n**2. Calculate Final Moles**\n"
|
| 375 |
+
f"Using $X_A = {X}$ as the basis:\n\n"
|
| 376 |
+
f"$N_A = N_{{A0}}(1 - X_A) = {N_A0}(1 - {X}) = \\mathbf{{{round(N_A, 2)}}}$ mol\n"
|
| 377 |
+
f"$N_B = N_{{B0}} - (b/a)N_{{A0}}X_A = {N_B0} - ({b}/{a})({N_A0})({X}) = \\mathbf{{{round(N_B, 2)}}}$ mol\n"
|
| 378 |
+
f"$N_C = N_{{C0}} + (c/a)N_{{A0}}X_A = {N_C0} + ({c}/{a})({N_A0})({X}) = \\mathbf{{{round(N_C, 2)}}}$ mol\n"
|
| 379 |
+
f"$N_D = N_{{D0}} + (d/a)N_{{A0}}X_A = {N_D0} + ({d}/{a})({N_A0})({X}) = \\mathbf{{{round(N_D, 2)}}}$ mol"
|
| 380 |
+
)
|
| 381 |
+
else:
|
| 382 |
+
solution_step2_calculation = (
|
| 383 |
+
f"\n\n**2. Calculate Final Moles**\n"
|
| 384 |
+
f"Using $X_B = {X}$ as the basis:\n\n"
|
| 385 |
+
f"$N_A = N_{{A0}} - (a/b)N_{{B0}}X_B = {N_A0} - ({a}/{b})({N_B0})({X}) = \\mathbf{{{round(N_A, 2)}}}$ mol\n"
|
| 386 |
+
f"$N_B = N_{{B0}}(1 - X_B) = {N_B0}(1 - {X}) = \\mathbf{{{round(N_B, 2)}}}$ mol\n"
|
| 387 |
+
f"$N_C = N_{{C0}} + (c/b)N_{{B0}}X_B = {N_C0} + ({c}/{b})({N_B0})({X}) = \\mathbf{{{round(N_C, 2)}}}$ mol\n"
|
| 388 |
+
f"$N_D = N_{{D0}} + (d/b)N_{{B0}}X_B = {N_D0} + ({d}/{b})({N_B0})({X}) = \\mathbf{{{round(N_D, 2)}}}$ mol"
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
solution = f"### **Solution**\n\n{solution_step1_identification}{solution_step2_calculation}{solution_final_answer}"
|
| 392 |
+
|
| 393 |
+
return question, solution
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
# Template 4 (Advanced)
|
| 397 |
+
def template_gas_phase_concentration():
|
| 398 |
+
"""
|
| 399 |
+
Gas-Phase Concentration with Volumetric Change
|
| 400 |
+
|
| 401 |
+
Scenario:
|
| 402 |
+
For gas-phase reactions with a change in the total number of moles, the
|
| 403 |
+
volumetric flow rate varies with conversion. This template tests the ability
|
| 404 |
+
to calculate outlet concentrations in an isothermal, isobaric flow system
|
| 405 |
+
using the specialized gas-phase concentration equations from Fogler, which
|
| 406 |
+
account for this volumetric change via the parameter epsilon ($\\epsilon$).
|
| 407 |
+
|
| 408 |
+
$C_A = C_{A0} \\frac{1 - X_A}{1 + \\epsilon X_A}$
|
| 409 |
+
$C_B = C_{A0} \\frac{\\Theta_B - (b/a)X_A}{1 + \\epsilon X_A}$
|
| 410 |
+
|
| 411 |
+
Returns:
|
| 412 |
+
tuple: A tuple containing:
|
| 413 |
+
- str: A question about calculating gas-phase outlet concentrations.
|
| 414 |
+
- str: A detailed solution showing the application of the correct formulas.
|
| 415 |
+
"""
|
| 416 |
+
# 1. Randomized Parameters
|
| 417 |
+
|
| 418 |
+
# Select unique names
|
| 419 |
+
reactant_A_name, reactant_B_name = random.sample(GAS_PHASE_REACTANTS, 2)
|
| 420 |
+
product_C_name = random.choice(PRODUCTS)
|
| 421 |
+
|
| 422 |
+
# Stoichiometric coefficients
|
| 423 |
+
a = random.choice([1, 2])
|
| 424 |
+
b = random.randint(1, 3)
|
| 425 |
+
c = random.randint(1, 2)
|
| 426 |
+
|
| 427 |
+
# Initial concentration (gases have lower concentrations, units are mol/dm^3)
|
| 428 |
+
C_A0 = round(random.uniform(0.05, 0.25), 3)
|
| 429 |
+
|
| 430 |
+
# Conversion
|
| 431 |
+
X_A = round(random.uniform(0.50, 0.90), 2)
|
| 432 |
+
|
| 433 |
+
# Theta_B must be in excess of the stoichiometric requirement
|
| 434 |
+
Theta_B = round((b / a) * random.uniform(1.2, 2.5), 2)
|
| 435 |
+
|
| 436 |
+
# Epsilon (ε) can be positive (expansion) or negative (contraction)
|
| 437 |
+
# Ensure it's not too close to zero to make the problem meaningful
|
| 438 |
+
epsilon = 0
|
| 439 |
+
while abs(epsilon) < 0.1:
|
| 440 |
+
epsilon = round(random.uniform(-0.5, 0.5), 2)
|
| 441 |
+
|
| 442 |
+
# 2. Core Calculations
|
| 443 |
+
denominator = 1 + epsilon * X_A
|
| 444 |
+
C_A = C_A0 * (1 - X_A) / denominator
|
| 445 |
+
C_B = C_A0 * (Theta_B - (b / a) * X_A) / denominator
|
| 446 |
+
|
| 447 |
+
# 3. Generate Question and Solution Strings
|
| 448 |
+
def format_species(coeff, name):
|
| 449 |
+
return f"{coeff if coeff > 1 else ''}{name}"
|
| 450 |
+
|
| 451 |
+
reaction_string = (
|
| 452 |
+
f"{format_species(a, reactant_A_name)} + {format_species(b, reactant_B_name)} -> "
|
| 453 |
+
f"{format_species(c, product_C_name)}"
|
| 454 |
+
)
|
| 455 |
+
|
| 456 |
+
question = (
|
| 457 |
+
f"The following elementary gas-phase reaction occurs in a steady-state PFR:\n"
|
| 458 |
+
f"**Reaction:** ${reaction_string}$\n\n"
|
| 459 |
+
f"The reaction is carried out **isothermally** and **isobarically**. The feed enters the reactor with an initial concentration of {reactant_A_name} of $C_{{A0}} = {C_A0}$ mol/dm³.\n\n"
|
| 460 |
+
f"The following parameters are known:\n"
|
| 461 |
+
f"- Molar feed ratio: $\\Theta_B = F_{{B0}}/F_{{A0}} = {Theta_B}$\n"
|
| 462 |
+
f"- Volumetric change parameter: $\\epsilon = {epsilon}$\n\n"
|
| 463 |
+
f"If the reaction achieves a final conversion of $X_A = {X_A}$, what are the outlet concentrations of {reactant_A_name} ($C_A$) and {reactant_B_name} ($C_B$)?"
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
solution = (
|
| 467 |
+
f"**Step 1:** Identify Given Information\n"
|
| 468 |
+
f"- Initial Concentration: $C_{{A0}} = {C_A0}$ mol/dm³\n"
|
| 469 |
+
f"- Conversion: $X_A = {X_A}$\n"
|
| 470 |
+
f"- Molar Feed Ratio: $\\Theta_B = {Theta_B}$\n"
|
| 471 |
+
f"- Volumetric Change Parameter: $\\epsilon = {epsilon}$\n"
|
| 472 |
+
f"- Stoichiometric Ratio: $b/a = {b}/{a} = {round(b/a, 2)}$\n\n"
|
| 473 |
+
|
| 474 |
+
f"**Step 2:** State the Governing Equations\n"
|
| 475 |
+
f"For an isothermal, isobaric gas-phase reaction with a change in the number of moles, the concentration of each species is a function of conversion ($X_A$) and the volumetric change parameter ($\\epsilon$). The term $(1 + \\epsilon X_A)$ in the denominator corrects for the change in volumetric flow rate.\n\n"
|
| 476 |
+
f"For reactant A: \n$C_A = C_{{A0}} \\frac{{1 - X_A}}{{1 + \\epsilon X_A}}$\n\n"
|
| 477 |
+
f"For reactant B: \n$C_B = C_{{A0}} \\frac{{\\Theta_B - (b/a)X_A}}{{1 + \\epsilon X_A}}$\n\n"
|
| 478 |
+
|
| 479 |
+
f"**Step 3:** Calculate the Denominator Term\n"
|
| 480 |
+
f"First, let's calculate the volumetric correction term, which is common to all species:\n"
|
| 481 |
+
f"$1 + \\epsilon X_A = 1 + ({epsilon})({X_A}) = {round(denominator, 4)}$\n\n"
|
| 482 |
+
|
| 483 |
+
f"**Step 4:** Calculate Outlet Concentrations\n\n"
|
| 484 |
+
f"**For {reactant_A_name} ($C_A$):**\n"
|
| 485 |
+
f"$C_A = {C_A0} \\frac{{1 - {X_A}}}{{{round(denominator, 4)}}} = {C_A0} \\frac{{{round(1 - X_A, 2)}}}{{{round(denominator, 4)}}} = \\mathbf{{{round(C_A, 4)}}}$ mol/dm³\n\n"
|
| 486 |
+
|
| 487 |
+
f"**For {reactant_B_name} ($C_B$):**\n"
|
| 488 |
+
f"First, calculate the numerator term for B: \n"
|
| 489 |
+
f"$\\Theta_B - (b/a)X_A = {Theta_B} - ({round(b/a, 2)})({X_A}) = {round(Theta_B - (b/a)*X_A, 4)}$\n"
|
| 490 |
+
f"Now, calculate the concentration:\n"
|
| 491 |
+
f"$C_B = {C_A0} \\frac{{{round(Theta_B - (b/a)*X_A, 4)}}}{{{round(denominator, 4)}}} = \\mathbf{{{round(C_B, 4)}}}$ mol/dm³\n\n"
|
| 492 |
+
|
| 493 |
+
f"**Final Answer**\n"
|
| 494 |
+
f"The outlet concentrations are:\n"
|
| 495 |
+
f"- **$C_A$:** ${round(C_A, 4)}$ mol/dm³\n"
|
| 496 |
+
f"- **$C_B$:** ${round(C_B, 4)}$ mol/dm³"
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
return question, solution
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def main():
|
| 503 |
+
"""
|
| 504 |
+
Generate numerous instances of each stoichiometry template with different random seeds
|
| 505 |
+
and write the results to a JSONL file.
|
| 506 |
+
"""
|
| 507 |
+
import json
|
| 508 |
+
import os
|
| 509 |
+
|
| 510 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 511 |
+
output_file = "testset/chemical_engineering/reaction_kinetics/stoichiometry.jsonl"
|
| 512 |
+
|
| 513 |
+
# Create the directory if it doesn't exist
|
| 514 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 515 |
+
|
| 516 |
+
# List of template functions with their ID and level
|
| 517 |
+
templates = [
|
| 518 |
+
(template_batch_moles_vs_conversion, "batch_moles_vs_conversion", "Easy"),
|
| 519 |
+
(template_flow_system_molar_flow_rates, "flow_rates_vs_conversion", "Intermediate"),
|
| 520 |
+
(template_limiting_reactant, "finding_limiting_reactant", "Intermediate"),
|
| 521 |
+
(template_gas_phase_concentration, "gas_phase_concentration", "Advanced"),
|
| 522 |
+
]
|
| 523 |
+
|
| 524 |
+
# List to store all generated problems
|
| 525 |
+
all_problems = []
|
| 526 |
+
|
| 527 |
+
# Generate problems for each template
|
| 528 |
+
for template_func, id_name, level in templates:
|
| 529 |
+
for _ in range(50):
|
| 530 |
+
# Generate a unique seed for each problem
|
| 531 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 532 |
+
random.seed(seed)
|
| 533 |
+
|
| 534 |
+
# Generate the problem and solution
|
| 535 |
+
question, solution = template_func()
|
| 536 |
+
|
| 537 |
+
# Create a JSON entry
|
| 538 |
+
problem_entry = {
|
| 539 |
+
"seed": seed,
|
| 540 |
+
"branch": "chemical_engineering",
|
| 541 |
+
"domain": "reaction_kinetics",
|
| 542 |
+
"area": "stoichiometry",
|
| 543 |
+
"id": id_name,
|
| 544 |
+
"level": level,
|
| 545 |
+
"question": question,
|
| 546 |
+
"solution": solution
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
# Add to the list of problems
|
| 550 |
+
all_problems.append(problem_entry)
|
| 551 |
+
|
| 552 |
+
# Shuffle the problems to mix templates and levels
|
| 553 |
+
random.shuffle(all_problems)
|
| 554 |
+
|
| 555 |
+
# Write all problems to a .jsonl file
|
| 556 |
+
with open(output_file, "w") as file:
|
| 557 |
+
for problem in all_problems:
|
| 558 |
+
file.write(json.dumps(problem))
|
| 559 |
+
file.write("\n")
|
| 560 |
+
|
| 561 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
if __name__ == "__main__":
|
| 565 |
+
main()
|
data/templates/branches/chemical_engineering/thermodynamics/heat_effects.py
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
from scipy.optimize import fsolve
|
| 3 |
+
from data.templates.branches.chemical_engineering.constants import SUBSTANCES_FOR_HEATING, SUBSTANCES_FOR_VAPORIZATION, HEATS_OF_FORMATION, REACTIONS, CP_PARAMS, COMBUSTION_REACTIONS
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# Template 1 (Easy)
|
| 7 |
+
def template_sensible_heat_constant_cp():
|
| 8 |
+
"""
|
| 9 |
+
Sensible Heat Calculation (Constant Heat Capacity)
|
| 10 |
+
|
| 11 |
+
Scenario:
|
| 12 |
+
This template calculates the sensible heat required to change the
|
| 13 |
+
temperature of a substance without changing its phase. It uses the
|
| 14 |
+
substance's actual specific heat capacity (Cp) for a physically
|
| 15 |
+
accurate problem.
|
| 16 |
+
|
| 17 |
+
The governing equation on a mass basis is:
|
| 18 |
+
Q = m * Cp * (T2 - T1)
|
| 19 |
+
Where:
|
| 20 |
+
- Q: Total heat transferred
|
| 21 |
+
- m: Mass of the substance
|
| 22 |
+
- Cp: Constant pressure specific heat capacity
|
| 23 |
+
- T1, T2: Initial and final temperatures
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
tuple: A tuple containing:
|
| 27 |
+
- str: A question asking to compute the sensible heat.
|
| 28 |
+
- str: A step-by-step solution showing the calculation.
|
| 29 |
+
"""
|
| 30 |
+
# 1. Parameterize the inputs using the substance list
|
| 31 |
+
substance_data = random.choice(SUBSTANCES_FOR_HEATING)
|
| 32 |
+
substance_name = substance_data["name"]
|
| 33 |
+
substance_state = substance_data["state"]
|
| 34 |
+
Cp = substance_data["Cp"] # J/(g·K) - Specific heat, not molar
|
| 35 |
+
|
| 36 |
+
# Generate a random mass in grams
|
| 37 |
+
m = round(random.uniform(50.0, 1000.0), 1) # grams
|
| 38 |
+
|
| 39 |
+
# Generate temperatures in Celsius
|
| 40 |
+
T1_C = round(random.uniform(10.0, 50.0), 1)
|
| 41 |
+
T2_C = round(random.uniform(T1_C + 30, 150.0), 1)
|
| 42 |
+
|
| 43 |
+
# 2. Perform the core calculation
|
| 44 |
+
delta_T = T2_C - T1_C
|
| 45 |
+
# Q will be in Joules (g * J/g·K * K)
|
| 46 |
+
Q_J = m * Cp * delta_T
|
| 47 |
+
# Convert to kilojoules for the final answer
|
| 48 |
+
Q_kJ = Q_J / 1000
|
| 49 |
+
|
| 50 |
+
# 3. Generate the question and solution strings
|
| 51 |
+
question = (
|
| 52 |
+
f"Calculate the heat in kJ required to raise the temperature of {m} g "
|
| 53 |
+
f"of {substance_state} {substance_name} from {T1_C}°C to {T2_C}°C. "
|
| 54 |
+
f"The specific heat capacity of {substance_name} is {Cp} J/g·K."
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
solution = (
|
| 58 |
+
f"**Step 1:** State the formula.\n"
|
| 59 |
+
f"Q = m * Cp * ΔT\n\n"
|
| 60 |
+
f"**Note:** We assume the specific heat capacity (Cp) is constant over this temperature range."
|
| 61 |
+
|
| 62 |
+
f"**Step 2:** List the given values.\n"
|
| 63 |
+
f"- Mass (m) = {m} g\n"
|
| 64 |
+
f"- Specific Heat Capacity (Cp) = {Cp} J/g·K\n"
|
| 65 |
+
f"- Initial Temperature (T1) = {T1_C}°C\n"
|
| 66 |
+
f"- Final Temperature (T2) = {T2_C}°C\n\n"
|
| 67 |
+
|
| 68 |
+
f"**Step 3:** Calculate the temperature difference (ΔT).\n"
|
| 69 |
+
f"A change in temperature has the same magnitude in Celsius and Kelvin.\n"
|
| 70 |
+
f"ΔT = T2 - T1 = {T2_C}°C - {T1_C}°C = {round(delta_T, 1)} K\n\n"
|
| 71 |
+
|
| 72 |
+
f"**Step 4:** Substitute the values into the formula to find the heat in Joules (J).\n"
|
| 73 |
+
f"Q = {m} g * {Cp} J/g·K * {round(delta_T, 1)} K\n"
|
| 74 |
+
f"Q = {round(Q_J, 1)} J\n\n"
|
| 75 |
+
|
| 76 |
+
f"**Step 5:** Convert the heat to kilojoules (kJ) as requested.\n"
|
| 77 |
+
f"Q = {round(Q_J, 1)} J * (1 kJ / 1000 J) = {round(Q_kJ, 2)} kJ\n\n"
|
| 78 |
+
|
| 79 |
+
f"**Answer:** The heat required is **{round(Q_kJ, 2)} kJ**."
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
return question, solution
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# Template 2 (Easy)
|
| 86 |
+
def template_latent_heat_vaporization():
|
| 87 |
+
"""
|
| 88 |
+
Latent Heat of Vaporization
|
| 89 |
+
|
| 90 |
+
Scenario:
|
| 91 |
+
This template calculates the heat required to cause a phase change
|
| 92 |
+
(vaporization) at a constant temperature and pressure. This energy,
|
| 93 |
+
known as latent heat, is used to overcome intermolecular forces
|
| 94 |
+
rather than to increase the substance's kinetic energy (temperature).
|
| 95 |
+
|
| 96 |
+
The governing equation is:
|
| 97 |
+
Q = n * ΔH_vap
|
| 98 |
+
Where:
|
| 99 |
+
- Q: Total heat absorbed
|
| 100 |
+
- n: Number of moles
|
| 101 |
+
- ΔH_vap: Molar heat of vaporization
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
tuple: A tuple containing:
|
| 105 |
+
- str: A question asking to compute the latent heat.
|
| 106 |
+
- str: A step-by-step solution showing the calculation.
|
| 107 |
+
"""
|
| 108 |
+
# 1. Parameterize the inputs using the substance list
|
| 109 |
+
substance_data = random.choice(SUBSTANCES_FOR_VAPORIZATION)
|
| 110 |
+
substance_name = substance_data["name"]
|
| 111 |
+
delta_H_vap = substance_data["delta_H_vap"] # in kJ/mol
|
| 112 |
+
|
| 113 |
+
# Generate a random number of moles
|
| 114 |
+
n = round(random.uniform(0.5, 5.0), 2) # moles
|
| 115 |
+
|
| 116 |
+
# 2. Perform the core calculation
|
| 117 |
+
# The result will be in kJ since ΔH_vap is in kJ/mol
|
| 118 |
+
Q_kJ = n * delta_H_vap
|
| 119 |
+
|
| 120 |
+
# 3. Generate the question and solution strings
|
| 121 |
+
question = (
|
| 122 |
+
f"How much heat in kJ is required to completely vaporize {n} moles of "
|
| 123 |
+
f"liquid {substance_name} at its normal boiling point? The molar heat of "
|
| 124 |
+
f"vaporization for {substance_name} is {delta_H_vap} kJ/mol."
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
solution = (
|
| 128 |
+
f"**Step 1:** State the formula.\n"
|
| 129 |
+
f"Q = n * ΔH_vap\n\n"
|
| 130 |
+
|
| 131 |
+
f"**Step 2:** List the given values.\n"
|
| 132 |
+
f"- Moles (n) = {n} mol\n"
|
| 133 |
+
f"- Molar Heat of Vaporization (ΔH_vap) = {delta_H_vap} kJ/mol\n\n"
|
| 134 |
+
|
| 135 |
+
f"**Step 3:** Substitute the values into the formula.\n"
|
| 136 |
+
f"Q = {n} mol * {delta_H_vap} kJ/mol\n\n"
|
| 137 |
+
|
| 138 |
+
f"**Step 4:** Calculate the total heat required.\n"
|
| 139 |
+
f"Q = {round(Q_kJ, 2)} kJ\n\n"
|
| 140 |
+
|
| 141 |
+
f"**Answer:** The total heat required to vaporize the {substance_name} is **{round(Q_kJ, 2)} kJ**."
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
return question, solution
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
# Template 3 (Easy)
|
| 148 |
+
def template_heat_of_reaction_formation():
|
| 149 |
+
"""
|
| 150 |
+
Standard Heat of Reaction from Heats of Formation
|
| 151 |
+
|
| 152 |
+
Scenario:
|
| 153 |
+
This template applies Hess's Law to find the standard heat of reaction
|
| 154 |
+
at 298.15 K (ΔH_rxn°). It is calculated by subtracting the sum of the
|
| 155 |
+
standard heats of formation (ΔH_f°) of the reactants from the sum of
|
| 156 |
+
the heats of formation of the products, with each being weighted by
|
| 157 |
+
its stoichiometric coefficient (v).
|
| 158 |
+
|
| 159 |
+
The governing equation is:
|
| 160 |
+
ΔH_rxn° = Σ(v * ΔH_f°)products - Σ(v * ΔH_f°)reactants
|
| 161 |
+
|
| 162 |
+
Returns:
|
| 163 |
+
tuple: A tuple containing:
|
| 164 |
+
- str: A question asking to compute the standard heat of reaction.
|
| 165 |
+
- str: A step-by-step solution showing the calculation.
|
| 166 |
+
"""
|
| 167 |
+
# 1. Parameterize the inputs by choosing a random reaction
|
| 168 |
+
reaction_data = random.choice(REACTIONS)
|
| 169 |
+
equation = reaction_data["equation"]
|
| 170 |
+
reactants = reaction_data["reactants"]
|
| 171 |
+
products = reaction_data["products"]
|
| 172 |
+
|
| 173 |
+
# 2. Perform the core calculation with full precision
|
| 174 |
+
products_enthalpy = sum(nu * HEATS_OF_FORMATION[species] for species, nu in products.items())
|
| 175 |
+
reactants_enthalpy = sum(nu * HEATS_OF_FORMATION[species] for species, nu in reactants.items())
|
| 176 |
+
|
| 177 |
+
# Final calculation uses the precise, unrounded intermediate values
|
| 178 |
+
delta_H_rxn = products_enthalpy - reactants_enthalpy
|
| 179 |
+
|
| 180 |
+
# 3. Generate the question and solution strings
|
| 181 |
+
required_species = list(reactants.keys()) + list(products.keys())
|
| 182 |
+
hf_list_for_question = "\n".join(
|
| 183 |
+
f"- {s}: {HEATS_OF_FORMATION[s]} kJ/mol" for s in sorted(list(set(required_species)))
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
# Question updated for clarity
|
| 187 |
+
question = (
|
| 188 |
+
f"Calculate the standard enthalpy of reaction, ΔH_rxn°, at 298.15 K (in kJ) for the reaction as written:\n"
|
| 189 |
+
f"{equation}\n\n"
|
| 190 |
+
f"Use the standard heats of formation (ΔH_f°) provided below:\n{hf_list_for_question}"
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
# Helper functions to format the solution steps
|
| 194 |
+
def format_sum(species_dict):
|
| 195 |
+
return " + ".join([f"({nu} * ΔH_f°[{s}])" for s, nu in species_dict.items()])
|
| 196 |
+
|
| 197 |
+
def format_calc(species_dict):
|
| 198 |
+
return " + ".join([f"({nu} * {HEATS_OF_FORMATION[s]})" for s, nu in species_dict.items()])
|
| 199 |
+
|
| 200 |
+
# Solution updated to use correct rounding procedures and units
|
| 201 |
+
solution = (
|
| 202 |
+
f"**Step 1:** State the formula.\n"
|
| 203 |
+
f"ΔH_rxn° = Σ(v * ΔH_f°)products - Σ(v * ΔH_f°)reactants\n\n"
|
| 204 |
+
|
| 205 |
+
f"**Step 2:** Calculate the total enthalpy of the products.\n"
|
| 206 |
+
f"Σ_products = {format_sum(products)}\n"
|
| 207 |
+
f"Σ_products = {format_calc(products)}\n"
|
| 208 |
+
f"Σ_products = {products_enthalpy:.3f} kJ\n\n"
|
| 209 |
+
|
| 210 |
+
f"**Step 3:** Calculate the total enthalpy of the reactants.\n"
|
| 211 |
+
f"Σ_reactants = {format_sum(reactants)}\n"
|
| 212 |
+
f"Σ_reactants = {format_calc(reactants)}\n"
|
| 213 |
+
f"Σ_reactants = {reactants_enthalpy:.3f} kJ\n\n"
|
| 214 |
+
|
| 215 |
+
f"**Step 4:** Calculate the standard enthalpy of reaction using the full-precision values.\n"
|
| 216 |
+
f"ΔH_rxn° = ({products_enthalpy:.3f}) - ({reactants_enthalpy:.3f})\n"
|
| 217 |
+
f"ΔH_rxn° = {round(delta_H_rxn, 2)} kJ\n\n"
|
| 218 |
+
|
| 219 |
+
f"**Answer:** The standard enthalpy of reaction is **{round(delta_H_rxn, 2)} kJ** for the reaction as written."
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
return question, solution
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
# Template 4 (Intermediate)
|
| 226 |
+
def template_sensible_heat_temp_dependent_cp():
|
| 227 |
+
"""
|
| 228 |
+
Sensible Heat (Temperature-Dependent Heat Capacity)
|
| 229 |
+
|
| 230 |
+
Scenario:
|
| 231 |
+
This template provides a more accurate calculation of sensible heat where
|
| 232 |
+
the heat capacity (Cp) is a polynomial function of temperature. The total
|
| 233 |
+
heat required is found by integrating this function over the temperature
|
| 234 |
+
range from T1 to T2.
|
| 235 |
+
|
| 236 |
+
The governing equation is:
|
| 237 |
+
Q = n * integral(Cp(T) dT) from T1 to T2
|
| 238 |
+
|
| 239 |
+
Returns:
|
| 240 |
+
tuple: A tuple containing:
|
| 241 |
+
- str: A question asking to compute the sensible heat via integration.
|
| 242 |
+
- str: A step-by-step solution showing the analytical integration.
|
| 243 |
+
"""
|
| 244 |
+
# 1. Parameterize the inputs
|
| 245 |
+
R = 8.314 # J/(mol·K)
|
| 246 |
+
substance_name = random.choice(list(CP_PARAMS.keys()))
|
| 247 |
+
params = CP_PARAMS[substance_name]
|
| 248 |
+
A, B, C, D = params["A"], params["B"], params["C"], params["D"]
|
| 249 |
+
|
| 250 |
+
n = round(random.uniform(1.0, 10.0), 2) # moles
|
| 251 |
+
T1 = round(random.uniform(298.15, 400.0), 2) # Kelvin
|
| 252 |
+
T2 = round(random.uniform(T1 + 200, 900.0), 2) # Kelvin
|
| 253 |
+
|
| 254 |
+
# 2. Perform the core calculation via analytical integration
|
| 255 |
+
# integral(A + BT + CT² + DT⁻²)dT = AT + (B/2)T² + (C/3)T³ - D/T
|
| 256 |
+
def integral_mean_cp_over_r(T):
|
| 257 |
+
term_a = A * T
|
| 258 |
+
term_b = (B / 2) * (T**2)
|
| 259 |
+
term_c = (C / 3) * (T**3)
|
| 260 |
+
term_d = -D / T if T != 0 else 0
|
| 261 |
+
return term_a + term_b + term_c + term_d
|
| 262 |
+
|
| 263 |
+
# Calculate the definite integral per mole
|
| 264 |
+
integral_val = R * (integral_mean_cp_over_r(T2) - integral_mean_cp_over_r(T1))
|
| 265 |
+
|
| 266 |
+
Q_J = n * integral_val # Total heat in Joules
|
| 267 |
+
Q_kJ = Q_J / 1000 # Convert to kilojoules
|
| 268 |
+
|
| 269 |
+
# 3. Generate the question and solution strings
|
| 270 |
+
# Dynamically create the Cp/R equation string for the question
|
| 271 |
+
cp_eq_str = f"A"
|
| 272 |
+
if B != 0: cp_eq_str += f" + B*T"
|
| 273 |
+
if C != 0: cp_eq_str += f" + C*T²"
|
| 274 |
+
if D != 0: cp_eq_str += f" + D*T⁻²"
|
| 275 |
+
|
| 276 |
+
question = (
|
| 277 |
+
f"Calculate the heat in kJ required to raise the temperature of {n} moles of "
|
| 278 |
+
f"{substance_name} from {T1} K to {T2} K. The molar heat capacity for "
|
| 279 |
+
f"{substance_name} is given by the equation:\n"
|
| 280 |
+
f" Cp/R = {cp_eq_str}\n"
|
| 281 |
+
f"Where the constants are: A={A}, B={B:.3g}, C={C:.3g}, D={D:.3g}"
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
solution = (
|
| 285 |
+
f"**Step 1:** State the integral formula for total heat (Q).\n"
|
| 286 |
+
f"Q = n * ∫[from T1 to T2] Cp(T) dT\n"
|
| 287 |
+
f"Given Cp(T) = R * ({cp_eq_str}), the integral is:\n"
|
| 288 |
+
f"Q = n * R * ∫[from {T1} to {T2}] ({A} + {B:.3g}*T {'+ ' + str(C) + '*T²' if C != 0 else ''} {'+ ' + str(D) + '*T⁻²' if D != 0 else ''}) dT\n\n"
|
| 289 |
+
|
| 290 |
+
f"**Step 2:** Perform the analytical integration.\n"
|
| 291 |
+
f"The indefinite integral of Cp(T)/R is: A*T + (B/2)*T² + (C/3)*T³ - D/T\n\n"
|
| 292 |
+
|
| 293 |
+
f"**Step 3:** Evaluate the definite integral from T1 to T2.\n"
|
| 294 |
+
f"∫ Cp(T) dT = R * [ (A*T₂ + (B/2)*T₂² + ...) - (A*T₁ + (B/2)*T₁² + ...) ]\n"
|
| 295 |
+
f"∫ Cp(T) dT = {R} J/mol·K * [({integral_mean_cp_over_r(T2):.2f}) - ({integral_mean_cp_over_r(T1):.2f})]\n"
|
| 296 |
+
f"∫ Cp(T) dT = {R} J/mol·K * ({(integral_mean_cp_over_r(T2) - integral_mean_cp_over_r(T1)):.2f}) K = {integral_val:.2f} J/mol\n\n"
|
| 297 |
+
|
| 298 |
+
f"**Step 4:** Calculate the total heat Q for {n} moles.\n"
|
| 299 |
+
f"Q = n * ({integral_val:.2f} J/mol) = {n} mol * {integral_val:.2f} J/mol = {Q_J:.2f} J\n\n"
|
| 300 |
+
|
| 301 |
+
f"**Step 5:** Convert the result to kilojoules.\n"
|
| 302 |
+
f"Q = {Q_J:.2f} J * (1 kJ / 1000 J) = {Q_kJ:.2f} kJ\n\n"
|
| 303 |
+
|
| 304 |
+
f"**Answer:** The heat required is **{Q_kJ:.2f} kJ**."
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
return question, solution
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
# Template 5 (Advanced)
|
| 311 |
+
def template_adiabatic_flame_temperature():
|
| 312 |
+
"""
|
| 313 |
+
Adiabatic Flame Temperature
|
| 314 |
+
|
| 315 |
+
Scenario:
|
| 316 |
+
This template calculates the theoretical maximum temperature the products
|
| 317 |
+
of a combustion reaction can reach if it occurs adiabatically (no heat loss).
|
| 318 |
+
This requires an energy balance: the heat released by the reaction at a
|
| 319 |
+
reference temperature (298.15 K) must be completely absorbed by the
|
| 320 |
+
product gases as sensible heat, raising their temperature from the
|
| 321 |
+
reference temperature to the final adiabatic flame temperature (T_ad).
|
| 322 |
+
|
| 323 |
+
Because the heat capacities of the products are temperature-dependent,
|
| 324 |
+
this problem requires an iterative, numerical solution.
|
| 325 |
+
|
| 326 |
+
Returns:
|
| 327 |
+
tuple: A tuple containing:
|
| 328 |
+
- str: A question asking to compute the adiabatic flame temperature.
|
| 329 |
+
- str: A step-by-step solution showing the numerical method.
|
| 330 |
+
"""
|
| 331 |
+
# 1. Parameterize inputs
|
| 332 |
+
R = 8.314 # J/(mol·K)
|
| 333 |
+
T_initial = 298.15 # K
|
| 334 |
+
|
| 335 |
+
reaction_data = random.choice(COMBUSTION_REACTIONS)
|
| 336 |
+
fuel = reaction_data["fuel"]
|
| 337 |
+
equation = reaction_data["equation"]
|
| 338 |
+
reactants = reaction_data["reactants"]
|
| 339 |
+
products = reaction_data["products"]
|
| 340 |
+
|
| 341 |
+
# 2. Perform the core calculation
|
| 342 |
+
# First, calculate the standard heat of reaction at 298.15 K
|
| 343 |
+
try:
|
| 344 |
+
products_enthalpy_298 = sum(nu * HEATS_OF_FORMATION[s] for s, nu in products.items())
|
| 345 |
+
reactants_enthalpy_298 = sum(nu * HEATS_OF_FORMATION[s] for s, nu in reactants.items())
|
| 346 |
+
delta_H_298 = products_enthalpy_298 - reactants_enthalpy_298 # result in kJ
|
| 347 |
+
except KeyError as e:
|
| 348 |
+
# Handle cases where data might be missing for a defined reaction
|
| 349 |
+
return (f"Data Error for {fuel} combustion.", f"Missing heat of formation for {e.args[0]}.")
|
| 350 |
+
|
| 351 |
+
# Define a function for the integral of Cp/R for a single species
|
| 352 |
+
def integral_mean_cp_over_r(T, A, B, C, D):
|
| 353 |
+
return A*T + (B/2)*T**2 + (C/3)*T**3 - D/T
|
| 354 |
+
|
| 355 |
+
# Define the function that represents the energy balance equation
|
| 356 |
+
# We want to find T_final where this function equals zero.
|
| 357 |
+
def energy_balance(T_final):
|
| 358 |
+
# Calculate sensible heat absorbed by products from T_initial to T_final
|
| 359 |
+
# Units will be J
|
| 360 |
+
sensible_heat_products = 0
|
| 361 |
+
for species, nu in products.items():
|
| 362 |
+
params = CP_PARAMS[species]
|
| 363 |
+
A, B, C, D = params["A"], params["B"], params["C"], params["D"]
|
| 364 |
+
|
| 365 |
+
# Integral from T_initial to T_final
|
| 366 |
+
integral_val = integral_mean_cp_over_r(T_final, A, B, C, D) - integral_mean_cp_over_r(T_initial, A, B, C, D)
|
| 367 |
+
sensible_heat_products += nu * R * integral_val
|
| 368 |
+
|
| 369 |
+
# Energy Balance: Sensible Heat Absorbed + Heat of Reaction = 0
|
| 370 |
+
# Convert heat of reaction from kJ to J for consistency
|
| 371 |
+
return sensible_heat_products + (delta_H_298 * 1000)
|
| 372 |
+
|
| 373 |
+
# Solve for the root of the energy_balance function
|
| 374 |
+
initial_guess = 2000 # A reasonable starting guess in Kelvin
|
| 375 |
+
adiabatic_temp_kelvin = fsolve(energy_balance, initial_guess)[0]
|
| 376 |
+
|
| 377 |
+
# 3. Generate the question and solution strings
|
| 378 |
+
question = (
|
| 379 |
+
f"{fuel} gas enters a furnace at {T_initial} K and is burned completely with "
|
| 380 |
+
f"the theoretical amount of dry air (also at {T_initial} K). Assuming the "
|
| 381 |
+
f"process is adiabatic and there is no shaft work, estimate the "
|
| 382 |
+
f"adiabatic flame temperature in Kelvin."
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
+
solution = (
|
| 386 |
+
f"**Step 1:** Write the balanced chemical equation including inert nitrogen from air.\n"
|
| 387 |
+
f" {equation}\n\n"
|
| 388 |
+
|
| 389 |
+
f"**Step 2:** Calculate the standard heat of reaction at {T_initial} K (ΔH_rxn°).\n"
|
| 390 |
+
f"Using the provided heats of formation, the calculation is:\n"
|
| 391 |
+
f"ΔH_rxn° = Σ(ν·ΔH_f°)products - Σ(ν·ΔH_f°)reactants = {delta_H_298:.2f} kJ\n\n"
|
| 392 |
+
|
| 393 |
+
f"**Step 3:** Set up the energy balance equation.\n"
|
| 394 |
+
f"For an adiabatic process, the heat released by the reaction must be absorbed as sensible heat by the products.\n"
|
| 395 |
+
f"Heat Absorbed by Products + Heat of Reaction = 0\n"
|
| 396 |
+
f" Σ[n_i * ∫[from {T_initial} to T_ad] Cp_i(T) dT]_products + ΔH_rxn° = 0\n\n"
|
| 397 |
+
|
| 398 |
+
f"**Step 4:** Solve the equation for the adiabatic flame temperature (T_ad).\n"
|
| 399 |
+
f"This equation is non-linear because Cp is a function of temperature. We must use a numerical root-finding algorithm to solve for T_ad where the energy balance function equals zero.\n"
|
| 400 |
+
f"An initial guess of {initial_guess} K is used for the solver.\n\n"
|
| 401 |
+
|
| 402 |
+
f"**Step 5:** State the result from the numerical solver.\n"
|
| 403 |
+
f"The solver finds the temperature T_ad that satisfies the energy balance.\n"
|
| 404 |
+
f"T_ad = {adiabatic_temp_kelvin:.2f} K\n\n"
|
| 405 |
+
|
| 406 |
+
f"**Answer:** The estimated adiabatic flame temperature is **{adiabatic_temp_kelvin:.2f} K**."
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
return question, solution
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
def main():
|
| 413 |
+
"""
|
| 414 |
+
Generate numerous instances of each heat effects template with different random seeds
|
| 415 |
+
and write the results to a JSONL file.
|
| 416 |
+
"""
|
| 417 |
+
import json
|
| 418 |
+
import os
|
| 419 |
+
|
| 420 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 421 |
+
output_file = "testset/chemical_engineering/thermodynamics/heat_effects.jsonl"
|
| 422 |
+
|
| 423 |
+
# Create the directory if it doesn't exist
|
| 424 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 425 |
+
|
| 426 |
+
# List of template functions with their ID and level
|
| 427 |
+
templates = [
|
| 428 |
+
(template_sensible_heat_constant_cp, "sensible_heat_constant_cp", "Easy"),
|
| 429 |
+
(template_latent_heat_vaporization, "latent_heat_vaporization", "Easy"),
|
| 430 |
+
(template_heat_of_reaction_formation, "heat_of_reaction_formation", "Easy"),
|
| 431 |
+
(template_sensible_heat_temp_dependent_cp, "sensible_heat_temp_dependent_cp", "Intermediate"),
|
| 432 |
+
(template_adiabatic_flame_temperature, "adiabatic_flame_temperature", "Advanced"),
|
| 433 |
+
]
|
| 434 |
+
|
| 435 |
+
# List to store all generated problems
|
| 436 |
+
all_problems = []
|
| 437 |
+
|
| 438 |
+
# Generate problems for each template
|
| 439 |
+
for template_func, id_name, level in templates:
|
| 440 |
+
for _ in range(50):
|
| 441 |
+
# Generate a unique seed for each problem
|
| 442 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 443 |
+
random.seed(seed)
|
| 444 |
+
|
| 445 |
+
# Generate the problem and solution
|
| 446 |
+
question, solution = template_func()
|
| 447 |
+
|
| 448 |
+
# Create a JSON entry
|
| 449 |
+
problem_entry = {
|
| 450 |
+
"seed": seed,
|
| 451 |
+
"branch": "chemical_engineering",
|
| 452 |
+
"domain": "thermodynamics",
|
| 453 |
+
"area": "heat_effects",
|
| 454 |
+
"id": id_name,
|
| 455 |
+
"level": level,
|
| 456 |
+
"question": question,
|
| 457 |
+
"solution": solution
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
# Add to the list of problems
|
| 461 |
+
all_problems.append(problem_entry)
|
| 462 |
+
|
| 463 |
+
# Shuffle the problems to mix templates and levels
|
| 464 |
+
random.shuffle(all_problems)
|
| 465 |
+
|
| 466 |
+
# Write all problems to a .jsonl file
|
| 467 |
+
with open(output_file, "w") as file:
|
| 468 |
+
for problem in all_problems:
|
| 469 |
+
file.write(json.dumps(problem))
|
| 470 |
+
file.write("\n")
|
| 471 |
+
|
| 472 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
if __name__ == "__main__":
|
| 476 |
+
main()
|
data/templates/branches/chemical_engineering/thermodynamics/volumetric_properties_pure_fluids.py
ADDED
|
@@ -0,0 +1,701 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import numpy as np
|
| 3 |
+
import math
|
| 4 |
+
from data.templates.branches.chemical_engineering.constants import GAS_PHASE_REACTANTS, THERMO_SUBSTANCES, CRITICAL_PROPERTIES
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# Template 1 (Easy)
|
| 8 |
+
def template_ideal_gas_volume():
|
| 9 |
+
"""
|
| 10 |
+
Ideal Gas Law Volume Calculation
|
| 11 |
+
|
| 12 |
+
Scenario:
|
| 13 |
+
The Ideal Gas Law, PV = nRT, is a fundamental equation of state that
|
| 14 |
+
describes the behavior of many gases. In this scenario, the pressure,
|
| 15 |
+
temperature, and number of moles of a gas are provided. The objective
|
| 16 |
+
is to apply the Ideal Gas Law to calculate the volume the gas occupies
|
| 17 |
+
using the formula:
|
| 18 |
+
|
| 19 |
+
V = nRT / P
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
tuple: A tuple containing:
|
| 23 |
+
- str: A question asking to compute the gas volume.
|
| 24 |
+
- str: A step-by-step solution showing the calculation.
|
| 25 |
+
"""
|
| 26 |
+
# 1. Parameterize the inputs with random values
|
| 27 |
+
gas_name = random.choice(GAS_PHASE_REACTANTS)
|
| 28 |
+
# Moles of gas
|
| 29 |
+
n = round(random.uniform(0.5, 5.0), 2)
|
| 30 |
+
# Temperature in Kelvin
|
| 31 |
+
T_k = round(random.uniform(273.15, 500.0), 2)
|
| 32 |
+
# Pressure in Pascals (for calculation)
|
| 33 |
+
P_pa = round(random.uniform(100000, 500000))
|
| 34 |
+
# Pressure in Kilopascals (for the question text)
|
| 35 |
+
P_kpa = round(P_pa / 1000, 1)
|
| 36 |
+
|
| 37 |
+
# Define the ideal gas constant in SI units
|
| 38 |
+
R = 8.314 # Pa·m³/(mol·K)
|
| 39 |
+
|
| 40 |
+
# 2. Perform the core calculation
|
| 41 |
+
# Volume will be in cubic meters (m³)
|
| 42 |
+
V_m3 = (n * R * T_k) / P_pa
|
| 43 |
+
# Convert volume to Liters for the final answer
|
| 44 |
+
V_L = V_m3 * 1000
|
| 45 |
+
|
| 46 |
+
# 3. Generate the question and solution strings
|
| 47 |
+
question = (
|
| 48 |
+
f"Calculate the volume in liters occupied by {n} moles of {gas_name} gas "
|
| 49 |
+
f"at a temperature of {T_k} K and a pressure of {P_kpa} kPa. "
|
| 50 |
+
f"Assume the gas behaves ideally."
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
solution = (
|
| 54 |
+
f"**Step 1:** State the Ideal Gas Law formula solved for volume (V).\n"
|
| 55 |
+
f"The formula is V = \\frac{{nRT}}{{P}}\n\n"
|
| 56 |
+
|
| 57 |
+
f"**Step 2:** List the given values and the ideal gas constant, ensuring consistent SI units.\n"
|
| 58 |
+
f"- Moles (n) = {n} mol\n"
|
| 59 |
+
f"- Temperature (T) = {T_k} K\n"
|
| 60 |
+
f"- Pressure (P) = {P_kpa} kPa = {P_pa} Pa\n"
|
| 61 |
+
f"- Ideal Gas Constant (R) = {R} Pa·m³/(mol·K)\n\n"
|
| 62 |
+
|
| 63 |
+
f"**Step 3:** Substitute the values into the equation.\n"
|
| 64 |
+
f"V = \\frac{{({n} \\text{{ mol}}) \\times ({R} \\text{{ Pa·m³/mol·K}}) \\times ({T_k} \\text{{ K}})}}{{{P_pa} \\text{{ Pa}}}}\n\n"
|
| 65 |
+
|
| 66 |
+
f"**Step 4:** Calculate the volume in cubic meters.\n"
|
| 67 |
+
f"V = {round(V_m3, 5)} \\text{{ m³}}\n\n"
|
| 68 |
+
|
| 69 |
+
f"**Step 5:** Convert the volume to liters as requested.\n"
|
| 70 |
+
f"Since 1 \\text{{ m³}} = 1000 \\text{{ L}}:\n"
|
| 71 |
+
f"V = {round(V_m3, 5)} \\text{{ m³}} \\times 1000 \\frac{{\\text{{L}}}}{{\\text{{m³}}}} = {round(V_L, 2)} \\text{{ L}}\n\n"
|
| 72 |
+
|
| 73 |
+
f"**Answer:** The volume occupied by the gas is **{round(V_L, 2)} liters**."
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
return question, solution
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# Template 2 (Easy)
|
| 80 |
+
def template_two_phase_specific_volume():
|
| 81 |
+
"""
|
| 82 |
+
Specific Volume of a Two-Phase Mixture
|
| 83 |
+
|
| 84 |
+
Scenario:
|
| 85 |
+
When a substance exists as a mixture of liquid and vapor in equilibrium
|
| 86 |
+
(a saturated mixture), its overall specific volume (V) depends on the
|
| 87 |
+
proportions of the liquid and vapor phases. This proportion is defined
|
| 88 |
+
by the quality (x), which is the mass fraction of vapor. This template
|
| 89 |
+
provides the specific volumes of the saturated liquid (V^l) and
|
| 90 |
+
saturated vapor (V^v) along with the quality, and asks to calculate
|
| 91 |
+
the overall specific volume.
|
| 92 |
+
|
| 93 |
+
The governing equation is:
|
| 94 |
+
V = (1-x)V^l + xV^v
|
| 95 |
+
|
| 96 |
+
Returns:
|
| 97 |
+
tuple: A tuple containing:
|
| 98 |
+
- str: A question asking to compute the mixture's specific volume.
|
| 99 |
+
- str: A step-by-step solution showing the calculation.
|
| 100 |
+
"""
|
| 101 |
+
# 1. Parameterize the inputs with random values
|
| 102 |
+
substance = random.choice(THERMO_SUBSTANCES)
|
| 103 |
+
# Specific volume of saturated liquid in m³/kg
|
| 104 |
+
V_l = round(random.uniform(0.001, 0.002), 5)
|
| 105 |
+
# Specific volume of saturated vapor in m³/kg
|
| 106 |
+
V_v = round(random.uniform(0.05, 2.0), 3)
|
| 107 |
+
# Quality (mass fraction of vapor)
|
| 108 |
+
x = round(random.uniform(0.1, 0.9), 2)
|
| 109 |
+
|
| 110 |
+
# 2. Perform the core calculation
|
| 111 |
+
V = (1 - x) * V_l + x * V_v
|
| 112 |
+
|
| 113 |
+
# 3. Generate the question and solution strings
|
| 114 |
+
question = (
|
| 115 |
+
f"A closed vessel contains a saturated mixture of {substance} at a constant pressure. "
|
| 116 |
+
f"The specific volume of the saturated liquid is {V_l} m³/kg and the "
|
| 117 |
+
f"specific volume of the saturated vapor is {V_v} m³/kg. If the quality "
|
| 118 |
+
f"of the mixture is {x*100:.0f}%, what is the overall specific volume of the mixture?"
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
solution = (
|
| 122 |
+
f"**Step 1:** State the formula for the specific volume of a saturated mixture.\n"
|
| 123 |
+
f"The formula is V = (1-x)V^l + xV^v, where V^l is the saturated liquid specific volume and V^v is the saturated vapor specific volume.\n\n"
|
| 124 |
+
|
| 125 |
+
f"**Step 2:** List the given values.\n"
|
| 126 |
+
f"- Quality (x) = {x}\n"
|
| 127 |
+
f"- Saturated liquid specific volume (V^l) = {V_l} m³/kg\n"
|
| 128 |
+
f"- Saturated vapor specific volume (V^v) = {V_v} m³/kg\n\n"
|
| 129 |
+
|
| 130 |
+
f"**Step 3:** Substitute the values into the equation.\n"
|
| 131 |
+
f"V = (1 - {x})({V_l} \\text{{ m³/kg}}) + ({x})({V_v} \\text{{ m³/kg}})\n"
|
| 132 |
+
f"V = ({(1-x):.2f})({V_l}) + ({x})({V_v})\n"
|
| 133 |
+
f"V = {round((1-x)*V_l, 5)} + {round(x*V_v, 5)}\n\n"
|
| 134 |
+
|
| 135 |
+
f"**Step 4:** Calculate the final specific volume.\n"
|
| 136 |
+
f"V = {round(V, 5)} \\text{{ m³/kg}}\n\n"
|
| 137 |
+
|
| 138 |
+
f"**Answer:** The overall specific volume of the mixture is **{round(V, 5)} m³/kg**."
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
return question, solution
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# Template 3 (Easy)
|
| 145 |
+
def template_rackett_equation_volume():
|
| 146 |
+
"""
|
| 147 |
+
Rackett Equation for Saturated Liquid Volume
|
| 148 |
+
|
| 149 |
+
Scenario:
|
| 150 |
+
The Rackett equation is a generalized correlation used to estimate the
|
| 151 |
+
molar volume of a saturated liquid when experimental data is not
|
| 152 |
+
available. It relies on the substance's critical properties.
|
| 153 |
+
|
| 154 |
+
This template provides the critical temperature (Tc), critical volume (Vc),
|
| 155 |
+
and critical compressibility factor (Zc) for a substance. The goal is to
|
| 156 |
+
calculate the saturated liquid molar volume (V_sat) at a given
|
| 157 |
+
temperature (T).
|
| 158 |
+
|
| 159 |
+
The governing equation is:
|
| 160 |
+
V_sat = Vc * Zc**((1 - Tr)**0.2857)
|
| 161 |
+
Where:
|
| 162 |
+
- V_sat: Molar volume of the saturated liquid (cm³/mol)
|
| 163 |
+
- Vc: Critical volume (cm³/mol)
|
| 164 |
+
- Zc: Critical compressibility factor
|
| 165 |
+
- Tr: Reduced temperature (T / Tc)
|
| 166 |
+
|
| 167 |
+
Returns:
|
| 168 |
+
tuple: A tuple containing:
|
| 169 |
+
- str: A question asking to compute the saturated liquid volume.
|
| 170 |
+
- str: A step-by-step solution showing the calculation.
|
| 171 |
+
"""
|
| 172 |
+
# 1. Parameterize the inputs with random values
|
| 173 |
+
substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
|
| 174 |
+
properties = CRITICAL_PROPERTIES[substance_name]
|
| 175 |
+
Tc = properties["Tc"]
|
| 176 |
+
Vc = properties["Vc"]
|
| 177 |
+
Zc = properties["Zc"]
|
| 178 |
+
|
| 179 |
+
# Generate a random temperature below the critical temperature
|
| 180 |
+
T = round(random.uniform(0.5 * Tc, 0.95 * Tc), 2)
|
| 181 |
+
|
| 182 |
+
# 2. Perform the core calculation
|
| 183 |
+
# Calculate the reduced temperature
|
| 184 |
+
Tr = T / Tc
|
| 185 |
+
# Apply the Rackett equation
|
| 186 |
+
exponent = (1 - Tr)**0.2857
|
| 187 |
+
V_sat = Vc * (Zc**exponent)
|
| 188 |
+
|
| 189 |
+
# 3. Generate the question and solution strings
|
| 190 |
+
question = (
|
| 191 |
+
f"Estimate the molar volume of saturated liquid {substance_name} at {T} K "
|
| 192 |
+
f"using the Rackett equation. The critical properties for {substance_name} are:\n"
|
| 193 |
+
f"Tc = {Tc} K\n"
|
| 194 |
+
f"Vc = {Vc} cm³/mol\n"
|
| 195 |
+
f"Zc = {Zc}"
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
solution = (
|
| 199 |
+
f"**Step 1:** State the Rackett equation.\n"
|
| 200 |
+
f"V_sat = Vc * Zc**((1 - Tr)**0.2857)\n\n"
|
| 201 |
+
|
| 202 |
+
f"**Step 2:** List the given properties and calculate the reduced temperature (Tr).\n"
|
| 203 |
+
f"- Critical Temperature (Tc) = {Tc} K\n"
|
| 204 |
+
f"- Critical Volume (Vc) = {Vc} cm³/mol\n"
|
| 205 |
+
f"- Critical Compressibility (Zc) = {Zc}\n"
|
| 206 |
+
f"- Temperature (T) = {T} K\n\n"
|
| 207 |
+
f"Tr = T / Tc = {T} / {Tc} = {round(Tr, 4)}\n\n"
|
| 208 |
+
|
| 209 |
+
f"**Step 3:** Substitute the values into the Rackett equation.\n"
|
| 210 |
+
f"V_sat = {Vc} * {Zc}**((1 - {round(Tr, 4)})**0.2857)\n"
|
| 211 |
+
f"V_sat = {Vc} * {Zc}**({round(1 - Tr, 4)}**0.2857)\n"
|
| 212 |
+
f"V_sat = {Vc} * {Zc}**({round(exponent, 4)})\n"
|
| 213 |
+
f"V_sat = {Vc} * {round(Zc**exponent, 4)}\n\n"
|
| 214 |
+
|
| 215 |
+
f"**Step 4:** Calculate the final molar volume.\n"
|
| 216 |
+
f"V_sat = {round(V_sat, 2)} cm³/mol\n\n"
|
| 217 |
+
|
| 218 |
+
f"**Answer:** The estimated molar volume of saturated liquid {substance_name} at {T} K is **{round(V_sat, 2)} cm³/mol**."
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
return question, solution
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
# Template 4 (Intermediate)
|
| 225 |
+
def template_vdw_solve_for_pressure():
|
| 226 |
+
"""
|
| 227 |
+
Van der Waals Equation for Pressure Calculation
|
| 228 |
+
|
| 229 |
+
Scenario:
|
| 230 |
+
The van der Waals equation is an equation of state that improves upon the
|
| 231 |
+
ideal gas law by including terms for intermolecular attraction ('a') and
|
| 232 |
+
molecular volume ('b'). This makes it applicable to real fluids in both
|
| 233 |
+
liquid and gas phases.
|
| 234 |
+
|
| 235 |
+
This template provides the critical properties (Tc and Pc) for a substance,
|
| 236 |
+
along with a given temperature (T) and molar volume (V). The objective is
|
| 237 |
+
to first calculate the van der Waals parameters 'a' and 'b', and then
|
| 238 |
+
use them to find the pressure (P).
|
| 239 |
+
|
| 240 |
+
The governing equations are:
|
| 241 |
+
P = (R * T) / (V - b) - a / (V**2)
|
| 242 |
+
a = (27 * R**2 * Tc**2) / (64 * Pc)
|
| 243 |
+
b = (R * Tc) / (8 * Pc)
|
| 244 |
+
|
| 245 |
+
Returns:
|
| 246 |
+
tuple: A tuple containing:
|
| 247 |
+
- str: A question asking to compute the pressure.
|
| 248 |
+
- str: A step-by-step solution showing the calculation.
|
| 249 |
+
"""
|
| 250 |
+
# 1. Parameterize the inputs
|
| 251 |
+
# Gas constant in L·bar/(mol·K)
|
| 252 |
+
R = 0.08314
|
| 253 |
+
|
| 254 |
+
substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
|
| 255 |
+
properties = CRITICAL_PROPERTIES[substance_name]
|
| 256 |
+
Tc = properties["Tc"]
|
| 257 |
+
Pc = properties["Pc"]
|
| 258 |
+
|
| 259 |
+
# Generate a random temperature, avoiding the critical region (0.95Tc to 1.05Tc)
|
| 260 |
+
while True:
|
| 261 |
+
T = round(random.uniform(0.8 * Tc, 2.5 * Tc), 2)
|
| 262 |
+
# Avoid temperatures too close to critical point where VdW equation is less accurate
|
| 263 |
+
if not (0.95 * Tc <= T <= 1.05 * Tc):
|
| 264 |
+
break
|
| 265 |
+
|
| 266 |
+
# First, calculate 'b' to ensure V > b
|
| 267 |
+
b = (R * Tc) / (8 * Pc)
|
| 268 |
+
# Generate a random molar volume greater than b
|
| 269 |
+
V = round(random.uniform(1.5 * b, 150 * b), 4)
|
| 270 |
+
|
| 271 |
+
# 2. Perform the core calculation
|
| 272 |
+
# Calculate parameter 'a'
|
| 273 |
+
a = (27 * (R**2) * (Tc**2)) / (64 * Pc)
|
| 274 |
+
|
| 275 |
+
# Calculate pressure using the Van der Waals equation
|
| 276 |
+
P = (R * T) / (V - b) - a / (V**2)
|
| 277 |
+
|
| 278 |
+
# 3. Generate the question and solution strings
|
| 279 |
+
question = (
|
| 280 |
+
f"Using the van der Waals equation of state, calculate the pressure in bar "
|
| 281 |
+
f"exerted by {substance_name} at a temperature of {T} K and a molar volume "
|
| 282 |
+
f"of {V} L/mol. The critical constants for {substance_name} are:\n"
|
| 283 |
+
f"Tc = {Tc} K\n"
|
| 284 |
+
f"Pc = {Pc} bar"
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
solution = (
|
| 288 |
+
f"**Step 1:** State the necessary formulas.\n"
|
| 289 |
+
f"Pressure: P = (R * T) / (V - b) - a / (V**2)\n"
|
| 290 |
+
f"Parameter 'a': a = (27 * R**2 * Tc**2) / (64 * Pc)\n"
|
| 291 |
+
f"Parameter 'b': b = (R * Tc) / (8 * Pc)\n\n"
|
| 292 |
+
|
| 293 |
+
f"**Step 2:** List the given values and the gas constant.\n"
|
| 294 |
+
f"- Temperature (T) = {T} K\n"
|
| 295 |
+
f"- Molar Volume (V) = {V} L/mol\n"
|
| 296 |
+
f"- Critical Temperature (Tc) = {Tc} K\n"
|
| 297 |
+
f"- Critical Pressure (Pc) = {Pc} bar\n"
|
| 298 |
+
f"- Gas Constant (R) = {R} L·bar/(mol·K)\n\n"
|
| 299 |
+
|
| 300 |
+
f"**Step 3:** Calculate the co-volume parameter 'b'.\n"
|
| 301 |
+
f"b = ({R} * {Tc}) / (8 * {Pc}) = {round(b, 5)} L/mol\n\n"
|
| 302 |
+
|
| 303 |
+
f"**Step 4:** Calculate the attraction parameter 'a'.\n"
|
| 304 |
+
f"a = (27 * ({R})**2 * ({Tc})**2) / (64 * {Pc}) = {round(a, 4)} L²·bar/mol²\n\n"
|
| 305 |
+
|
| 306 |
+
f"**Step 5:** Substitute all values into the van der Waals equation to find P.\n"
|
| 307 |
+
f"P = ({R} * {T}) / ({V} - {round(b, 5)}) - {round(a, 4)} / ({V})**2\n"
|
| 308 |
+
f"P = {round(R * T, 2)} / {round(V - b, 5)} - {round(a, 4)} / {round(V**2, 5)}\n"
|
| 309 |
+
f"P = {round((R * T) / (V - b), 2)} - {round(a / (V**2), 2)}\n\n"
|
| 310 |
+
|
| 311 |
+
f"**Step 6:** Calculate the final pressure.\n"
|
| 312 |
+
f"P = {round(P, 2)} bar\n\n"
|
| 313 |
+
|
| 314 |
+
f"**Answer:** The pressure exerted by the {substance_name} is **{round(P, 2)} bar**."
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
return question, solution
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
# Templat 5 (Intermediate)
|
| 321 |
+
def template_pitzer_correlation_z():
|
| 322 |
+
"""
|
| 323 |
+
Compressibility Factor from Pitzer's Correlation
|
| 324 |
+
|
| 325 |
+
Scenario:
|
| 326 |
+
The Pitzer correlation is a widely used application of the principle of
|
| 327 |
+
corresponding states to estimate the properties of real fluids. It
|
| 328 |
+
improves upon two-parameter correlations by introducing the acentric
|
| 329 |
+
factor (omega), which accounts for the non-sphericity of molecules.
|
| 330 |
+
|
| 331 |
+
This template uses a simplified form of the Pitzer correlation, valid
|
| 332 |
+
for gases at low to moderate pressures, to calculate the compressibility
|
| 333 |
+
factor (Z).
|
| 334 |
+
|
| 335 |
+
The governing equations are:
|
| 336 |
+
Z = 1 + (Pr / Tr) * (B0 + omega * B1)
|
| 337 |
+
B0 = 0.083 - (0.422 / Tr**1.6)
|
| 338 |
+
B1 = 0.139 - (0.172 / Tr**4.2)
|
| 339 |
+
Where:
|
| 340 |
+
- Z: Compressibility factor
|
| 341 |
+
- Tr, Pr: Reduced temperature and pressure
|
| 342 |
+
- omega: Acentric factor
|
| 343 |
+
- B0, B1: Second virial coefficients
|
| 344 |
+
|
| 345 |
+
Returns:
|
| 346 |
+
tuple: A tuple containing:
|
| 347 |
+
- str: A question asking to compute the compressibility factor.
|
| 348 |
+
- str: A step-by-step solution showing the calculation.
|
| 349 |
+
"""
|
| 350 |
+
# 1. Parameterize the inputs
|
| 351 |
+
R = 0.08314 # L·bar/(mol·K)
|
| 352 |
+
|
| 353 |
+
substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
|
| 354 |
+
properties = CRITICAL_PROPERTIES[substance_name]
|
| 355 |
+
Tc = properties["Tc"]
|
| 356 |
+
Pc = properties["Pc"]
|
| 357 |
+
omega = properties["omega"]
|
| 358 |
+
|
| 359 |
+
# Generate random T and P in the gas phase (Tr > 1) and at low-moderate pressure
|
| 360 |
+
Tr = round(random.uniform(1.1, 3.0), 3)
|
| 361 |
+
Pr = round(random.uniform(0.1, 2.0), 3)
|
| 362 |
+
T = round(Tr * Tc, 2)
|
| 363 |
+
P = round(Pr * Pc, 2)
|
| 364 |
+
|
| 365 |
+
# 2. Perform the core calculation
|
| 366 |
+
# Calculate virial coefficients
|
| 367 |
+
B0 = 0.083 - 0.422 / (Tr**1.6)
|
| 368 |
+
B1 = 0.139 - 0.172 / (Tr**4.2)
|
| 369 |
+
# Calculate compressibility factor
|
| 370 |
+
Z = 1 + (Pr / Tr) * (B0 + omega * B1)
|
| 371 |
+
# Optional extension: Calculate molar volume
|
| 372 |
+
V = (Z * R * T) / P
|
| 373 |
+
|
| 374 |
+
# 3. Generate the question and solution strings
|
| 375 |
+
question = (
|
| 376 |
+
f"For {substance_name} at a temperature of {T} K and a pressure of {P} bar, "
|
| 377 |
+
f"determine the compressibility factor, Z, using the Pitzer correlation for the "
|
| 378 |
+
f"second virial coefficient. The properties for {substance_name} are:\n"
|
| 379 |
+
f"Critical Temperature (Tc) = {Tc} K\n"
|
| 380 |
+
f"Critical Pressure (Pc) = {Pc} bar\n"
|
| 381 |
+
f"Acentric Factor (ω) = {omega}"
|
| 382 |
+
)
|
| 383 |
+
|
| 384 |
+
solution = (
|
| 385 |
+
f"**Step 1:** State the governing formulas.\n"
|
| 386 |
+
f"Z = 1 + (Pr / Tr) * (B0 + omega * B1)\n"
|
| 387 |
+
f"B0 = 0.083 - (0.422 / Tr**1.6)\n"
|
| 388 |
+
f"B1 = 0.139 - (0.172 / Tr**4.2)\n\n"
|
| 389 |
+
|
| 390 |
+
f"**Step 2:** Calculate the reduced temperature (Tr) and reduced pressure (Pr).\n"
|
| 391 |
+
f"Tr = T / Tc = {T} K / {Tc} K = {round(Tr, 4)}\n"
|
| 392 |
+
f"Pr = P / Pc = {P} bar / {Pc} bar = {round(Pr, 4)}\n\n"
|
| 393 |
+
|
| 394 |
+
f"**Step 3:** Calculate the virial equation coefficients, B0 and B1.\n"
|
| 395 |
+
f"B0 = 0.083 - (0.422 / {round(Tr, 4)}**1.6) = {round(B0, 4)}\n"
|
| 396 |
+
f"B1 = 0.139 - (0.172 / {round(Tr, 4)}**4.2) = {round(B1, 4)}\n\n"
|
| 397 |
+
|
| 398 |
+
f"**Step 4:** Substitute all values to calculate the compressibility factor (Z).\n"
|
| 399 |
+
f"Z = 1 + ({round(Pr, 4)} / {round(Tr, 4)}) * ({round(B0, 4)} + {omega} * {round(B1, 4)})\n"
|
| 400 |
+
f"Z = 1 + {round(Pr / Tr, 4)} * ({round(B0 + omega * B1, 4)})\n"
|
| 401 |
+
f"Z = {round(Z, 4)}\n\n"
|
| 402 |
+
|
| 403 |
+
f"(Optional) **Step 5:** Calculate the molar volume (V).\n"
|
| 404 |
+
f"V = Z * R * T / P = ({round(Z, 4)} * {R} * {T}) / {P} = {round(V, 4)} L/mol\n\n"
|
| 405 |
+
|
| 406 |
+
f"**Answer:** The compressibility factor, Z, for {substance_name} at the given conditions is **{round(Z, 4)}**."
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
return question, solution
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
# Template 6 (Advanced)
|
| 413 |
+
def template_vdw_solve_for_volume():
|
| 414 |
+
"""
|
| 415 |
+
Molar Volume from the Van der Waals Equation
|
| 416 |
+
|
| 417 |
+
Scenario:
|
| 418 |
+
Solving for molar volume (V) from a cubic equation of state, given
|
| 419 |
+
temperature (T) and pressure (P), requires finding the roots of a
|
| 420 |
+
cubic polynomial, which is done with a numerical solver.
|
| 421 |
+
|
| 422 |
+
When the state (T, P) is below the critical point, the equation can yield
|
| 423 |
+
three positive real roots, corresponding to the saturated liquid volume, the
|
| 424 |
+
saturated vapor volume, and an unstable intermediate root.
|
| 425 |
+
|
| 426 |
+
The governing equation is cast into a polynomial form for root-finding:
|
| 427 |
+
V**3 + c2*V**2 + c1*V + c0 = 0
|
| 428 |
+
Where:
|
| 429 |
+
- c2 = -(b + R*T/P)
|
| 430 |
+
- c1 = a/P
|
| 431 |
+
- c0 = -(a*b)/P
|
| 432 |
+
|
| 433 |
+
Returns:
|
| 434 |
+
tuple: A tuple containing:
|
| 435 |
+
- str: A question asking to compute the possible molar volumes.
|
| 436 |
+
- str: A step-by-step solution showing the calculation and root analysis.
|
| 437 |
+
"""
|
| 438 |
+
# 1. Parameterize the inputs
|
| 439 |
+
R = 0.08314 # L·bar/(mol·K)
|
| 440 |
+
|
| 441 |
+
substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
|
| 442 |
+
properties = CRITICAL_PROPERTIES[substance_name]
|
| 443 |
+
Tc = properties["Tc"]
|
| 444 |
+
Pc = properties["Pc"]
|
| 445 |
+
|
| 446 |
+
# Choose a T and P below the critical point
|
| 447 |
+
Tr = random.uniform(0.85, 0.95)
|
| 448 |
+
T = round(Tr * Tc, 2)
|
| 449 |
+
Pr = random.uniform(Tr * 0.8, Tr * 0.9) # Estimate a realistic saturation pressure
|
| 450 |
+
P = round(Pr * Pc, 2)
|
| 451 |
+
|
| 452 |
+
# 2. Perform the core calculation
|
| 453 |
+
a = (27 * (R**2) * (Tc**2)) / (64 * Pc)
|
| 454 |
+
b = (R * Tc) / (8 * Pc)
|
| 455 |
+
|
| 456 |
+
# Coefficients of the cubic polynomial: V³ + c₂V² + c₁V + c₀ = 0
|
| 457 |
+
c2 = -(b + R * T / P)
|
| 458 |
+
c1 = a / P
|
| 459 |
+
c0 = -(a * b) / P
|
| 460 |
+
coeffs = [1, c2, c1, c0]
|
| 461 |
+
|
| 462 |
+
roots = np.roots(coeffs)
|
| 463 |
+
|
| 464 |
+
# Improved Root Handling: Filter for nearly-real, positive roots
|
| 465 |
+
tolerance = 1e-9
|
| 466 |
+
positive_real_roots = sorted([
|
| 467 |
+
root.real for root in roots if abs(root.imag) < tolerance and root.real > 0
|
| 468 |
+
])
|
| 469 |
+
|
| 470 |
+
V_f, V_g = 0, 0
|
| 471 |
+
if len(positive_real_roots) >= 2: # Typically 3 for subcritical
|
| 472 |
+
V_f = positive_real_roots[0]
|
| 473 |
+
V_g = positive_real_roots[-1] # Safely choose the largest
|
| 474 |
+
elif len(positive_real_roots) == 1:
|
| 475 |
+
V_g = positive_real_roots[0]
|
| 476 |
+
|
| 477 |
+
# 3. Generate the question and solution strings
|
| 478 |
+
question = (
|
| 479 |
+
f"A vessel of {substance_name} is held at a temperature of {T} K and a pressure of {P} bar. "
|
| 480 |
+
f"Using the van der Waals equation of state, determine the possible molar volumes (L/mol) "
|
| 481 |
+
f"for the liquid and/or vapor phases. The critical properties for {substance_name} are:\n"
|
| 482 |
+
f"Tc = {Tc} K\nPc = {Pc} bar"
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
solution = (
|
| 486 |
+
f"**Step 1:** Calculate the van der Waals parameters 'a' and 'b'.\n"
|
| 487 |
+
f"a = (27 * R² * Tc²) / (64 * Pc) = {round(a, 4)} L²·bar/mol²\n"
|
| 488 |
+
f"b = (R * Tc) / (8 * Pc) = {round(b, 5)} L/mol\n\n"
|
| 489 |
+
|
| 490 |
+
f"**Step 2:** Formulate the cubic equation: V³ + c₂V² + c₁V + c₀ = 0.\n"
|
| 491 |
+
f"The calculated coefficients, with their respective units, are:\n"
|
| 492 |
+
f"- c₂ = {round(c2, 4)} (L/mol)\n"
|
| 493 |
+
f"- c₁ = {round(c1, 4)} (L²/mol²)\n"
|
| 494 |
+
f"- c₀ = {round(c0, 6)} (L³/mol³)\n\n"
|
| 495 |
+
|
| 496 |
+
f"**Step 3:** Solve the polynomial for its roots using a numerical solver.\n"
|
| 497 |
+
f"The physically meaningful (positive, real) roots found are: "
|
| 498 |
+
f"{', '.join([f'{r:.4f}' for r in positive_real_roots])} L/mol\n\n"
|
| 499 |
+
|
| 500 |
+
f"**Step 4:** Interpret the physical significance of the roots.\n"
|
| 501 |
+
)
|
| 502 |
+
|
| 503 |
+
if len(positive_real_roots) >= 2:
|
| 504 |
+
solution += (
|
| 505 |
+
f"For a subcritical state, we expect multiple positive real roots.\n"
|
| 506 |
+
f"- The smallest root corresponds to the molar volume of the **liquid phase (Vf)**.\n"
|
| 507 |
+
f"- The largest root corresponds to the molar volume of the **vapor phase (Vg)**.\n"
|
| 508 |
+
f"- Any intermediate root lies on the thermodynamically unstable branch of the isotherm (where pressure incorrectly increases with volume) and is disregarded.\n\n"
|
| 509 |
+
f"**Answer:**\n"
|
| 510 |
+
f" - Saturated Liquid Volume (Vf) ≈ **{round(V_f, 4)} L/mol**\n"
|
| 511 |
+
f" - Saturated Vapor Volume (Vg) ≈ **{round(V_g, 4)} L/mol**"
|
| 512 |
+
)
|
| 513 |
+
elif len(positive_real_roots) == 1:
|
| 514 |
+
solution += (
|
| 515 |
+
f"A single positive real root indicates the substance exists in a single phase (gas or supercritical fluid).\n\n"
|
| 516 |
+
f"**Answer:**\n"
|
| 517 |
+
f" - Molar Volume (V) = **{round(V_g, 4)} L/mol**"
|
| 518 |
+
)
|
| 519 |
+
else:
|
| 520 |
+
solution += "No physically meaningful (positive, real) roots were found for these conditions, which may indicate an issue with the applicability of the model at this state."
|
| 521 |
+
|
| 522 |
+
return question, solution
|
| 523 |
+
|
| 524 |
+
|
| 525 |
+
# Template 7 (Advanced)
|
| 526 |
+
def template_work_isothermal_virial():
|
| 527 |
+
"""
|
| 528 |
+
Work of Isothermal Compression for a Virial Gas
|
| 529 |
+
|
| 530 |
+
Scenario:
|
| 531 |
+
Calculating the work (W) for a mechanically reversible, isothermal
|
| 532 |
+
process requires integrating the pressure (P) with respect to volume (V).
|
| 533 |
+
For a non-ideal gas described by the virial equation, the P-V
|
| 534 |
+
relationship is more complex than the ideal gas law, leading to a
|
| 535 |
+
different result for the work of compression.
|
| 536 |
+
|
| 537 |
+
This template calculates work by first determining the second virial
|
| 538 |
+
coefficient (B) and the initial/final state properties (V1, V2), and
|
| 539 |
+
then applying the analytical integral of the virial equation.
|
| 540 |
+
|
| 541 |
+
The governing equations are:
|
| 542 |
+
P = R*T * (1/V + B/V**2)
|
| 543 |
+
W = - integral(P dV) from V1 to V2
|
| 544 |
+
W = -[R*T*ln(V2/V1) - B*R*T*(1/V2 - 1/V1)]
|
| 545 |
+
|
| 546 |
+
Returns:
|
| 547 |
+
tuple: A tuple containing:
|
| 548 |
+
- str: A question asking to compute the work of compression.
|
| 549 |
+
- str: A step-by-step solution showing the calculation and comparison
|
| 550 |
+
to the ideal gas case.
|
| 551 |
+
"""
|
| 552 |
+
# 1. Parameterize the inputs
|
| 553 |
+
R = 0.08314 # L·bar/(mol·K)
|
| 554 |
+
|
| 555 |
+
substance_name = random.choice(list(CRITICAL_PROPERTIES.keys()))
|
| 556 |
+
properties = CRITICAL_PROPERTIES[substance_name]
|
| 557 |
+
Tc = properties["Tc"]
|
| 558 |
+
Pc = properties["Pc"]
|
| 559 |
+
omega = properties["omega"]
|
| 560 |
+
|
| 561 |
+
# Generate conditions in the gas phase (Tr > 1) at moderate pressures
|
| 562 |
+
Tr = round(random.uniform(1.2, 3.0), 3)
|
| 563 |
+
T = round(Tr * Tc, 2)
|
| 564 |
+
|
| 565 |
+
P1_r = round(random.uniform(0.5, 2.0), 2)
|
| 566 |
+
P2_r = round(random.uniform(2.5, 5.0), 2)
|
| 567 |
+
P1 = round(P1_r * Pc, 2)
|
| 568 |
+
P2 = round(P2_r * Pc, 2)
|
| 569 |
+
|
| 570 |
+
# 2. Perform the core calculation
|
| 571 |
+
# Calculate B
|
| 572 |
+
B0 = 0.083 - 0.422 / (Tr**1.6)
|
| 573 |
+
B1 = 0.139 - 0.172 / (Tr**4.2)
|
| 574 |
+
B = (R * Tc / Pc) * (B0 + omega * B1) # Units: L/mol
|
| 575 |
+
|
| 576 |
+
# Calculate initial and final states
|
| 577 |
+
Z1 = 1 + (B * P1) / (R * T)
|
| 578 |
+
Z2 = 1 + (B * P2) / (R * T)
|
| 579 |
+
V1 = (Z1 * R * T) / P1
|
| 580 |
+
V2 = (Z2 * R * T) / P2
|
| 581 |
+
|
| 582 |
+
# Calculate work using the integrated virial equation
|
| 583 |
+
term1 = R * T * math.log(V2 / V1)
|
| 584 |
+
term2 = -B * R * T * ((1/V2) - (1/V1))
|
| 585 |
+
W_virial_Lbar = -(term1 + term2)
|
| 586 |
+
W_virial_J = W_virial_Lbar * 100 # Convert L·bar to Joules
|
| 587 |
+
|
| 588 |
+
# For comparison, calculate ideal gas work
|
| 589 |
+
W_ideal_Lbar = -R * T * math.log(P1 / P2)
|
| 590 |
+
W_ideal_J = W_ideal_Lbar * 100
|
| 591 |
+
|
| 592 |
+
# 3. Generate the question and solution strings
|
| 593 |
+
question = (
|
| 594 |
+
f"Calculate the work in J/mol required to isothermally and reversibly "
|
| 595 |
+
f"compress 1 mole of {substance_name} from {P1} bar to {P2} bar at a "
|
| 596 |
+
f"constant temperature of {T} K. Base your calculation on the virial "
|
| 597 |
+
f"equation of state truncated to two terms. The properties for {substance_name} are:\n"
|
| 598 |
+
f"Tc = {Tc} K, Pc = {Pc} bar, ω = {omega}"
|
| 599 |
+
)
|
| 600 |
+
|
| 601 |
+
solution = (
|
| 602 |
+
f"**Step 1:** Define the pressure from the virial equation and find the analytical integral for work.\n"
|
| 603 |
+
f"P = RT(1/V + B/V²)\n"
|
| 604 |
+
f"The integrated form is: W = -[RT·ln(V2/V1) - BRT(1/V2 - 1/V1)]\n\n"
|
| 605 |
+
|
| 606 |
+
f"**Step 2:** Calculate the second virial coefficient (B) at T = {T} K.\n"
|
| 607 |
+
f"Reduced Temperature, Tr = T/Tc = {T}/{Tc} = {Tr}\n"
|
| 608 |
+
f"B0 = 0.083 - 0.422 / ({Tr})**1.6 = {round(B0, 4)}\n"
|
| 609 |
+
f"B1 = 0.139 - 0.172 / ({Tr})**4.2 = {round(B1, 4)}\n"
|
| 610 |
+
f"B = (R·Tc/Pc) * (B0 + ω·B1) = {round(B, 5)} L/mol\n\n"
|
| 611 |
+
|
| 612 |
+
f"**Step 3:** Determine the initial (V1) and final (V2) molar volumes.\n"
|
| 613 |
+
f"Z1 = 1 + B·P1/(R·T) = 1 + ({round(B, 5)}*{P1})/({R}*{T}) = {round(Z1, 4)}\n"
|
| 614 |
+
f"V1 = Z1·R·T/P1 = {round(V1, 5)} L/mol\n"
|
| 615 |
+
f"Z2 = 1 + B·P2/(R·T) = 1 + ({round(B, 5)}*{P2})/({R}*{T}) = {round(Z2, 4)}\n"
|
| 616 |
+
f"V2 = Z2·R·T/P2 = {round(V2, 5)} L/mol\n\n"
|
| 617 |
+
|
| 618 |
+
f"**Step 4:** Substitute V1 and V2 into the integrated work equation.\n"
|
| 619 |
+
f"W = -[{round(R*T, 2)}·ln({round(V2, 5)}/{round(V1, 5)}) - {round(B*R*T, 3)}(1/{round(V2, 5)} - 1/{round(V1, 5)})]\n"
|
| 620 |
+
f"W = -[{round(term1, 2)} + {round(term2, 2)}] = {round(W_virial_Lbar, 2)} L·bar/mol\n\n"
|
| 621 |
+
|
| 622 |
+
f"**Step 5:** Convert the work to the required units (J/mol).\n"
|
| 623 |
+
f"Since 1 L·bar = 100 J:\n"
|
| 624 |
+
f"W = {round(W_virial_Lbar, 2)} L·bar/mol * 100 J/(L·bar) = {round(W_virial_J, 0)} J/mol\n\n"
|
| 625 |
+
|
| 626 |
+
f"**For Comparison:** The work required for an ideal gas is W_ideal = -RT·ln(P1/P2) = {round(W_ideal_J, 0)} J/mol. "
|
| 627 |
+
f"The deviation shows the effect of intermolecular forces accounted for by the virial equation.\n\n"
|
| 628 |
+
|
| 629 |
+
f"**Answer:** The required work of compression is approximately **{round(W_virial_J, 0)} J/mol**."
|
| 630 |
+
)
|
| 631 |
+
|
| 632 |
+
return question, solution
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
def main():
|
| 636 |
+
"""
|
| 637 |
+
Generate numerous instances of each volumetric properties of pure fluids template with
|
| 638 |
+
different random seeds and write the results to a JSONL file.
|
| 639 |
+
"""
|
| 640 |
+
import json
|
| 641 |
+
import os
|
| 642 |
+
|
| 643 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 644 |
+
output_file = "testset/chemical_engineering/thermodynamics/volumetric_properties_pure_fluids.jsonl"
|
| 645 |
+
|
| 646 |
+
# Create the directory if it doesn't exist
|
| 647 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 648 |
+
|
| 649 |
+
# List of template functions with their ID and level
|
| 650 |
+
templates = [
|
| 651 |
+
(template_ideal_gas_volume, "ideal_gas_volume", "Easy"),
|
| 652 |
+
(template_two_phase_specific_volume, "two_phase_specific_volume", "Easy"),
|
| 653 |
+
(template_rackett_equation_volume, "rackett_equation_volume", "Easy"),
|
| 654 |
+
(template_vdw_solve_for_pressure, "vdw_solve_for_pressure", "Intermediate"),
|
| 655 |
+
(template_pitzer_correlation_z, "pitzer_correlation_z", "Intermediate"),
|
| 656 |
+
(template_vdw_solve_for_volume, "vdw_solve_for_volume", "Advanced"),
|
| 657 |
+
(template_work_isothermal_virial, "work_isothermal_virial", "Advanced"),
|
| 658 |
+
]
|
| 659 |
+
|
| 660 |
+
# List to store all generated problems
|
| 661 |
+
all_problems = []
|
| 662 |
+
|
| 663 |
+
# Generate problems for each template
|
| 664 |
+
for template_func, id_name, level in templates:
|
| 665 |
+
for _ in range(50):
|
| 666 |
+
# Generate a unique seed for each problem
|
| 667 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 668 |
+
random.seed(seed)
|
| 669 |
+
|
| 670 |
+
# Generate the problem and solution
|
| 671 |
+
question, solution = template_func()
|
| 672 |
+
|
| 673 |
+
# Create a JSON entry
|
| 674 |
+
problem_entry = {
|
| 675 |
+
"seed": seed,
|
| 676 |
+
"branch": "chemical_engineering",
|
| 677 |
+
"domain": "thermodynamics",
|
| 678 |
+
"area": "volumetric_properties_pure_fluids",
|
| 679 |
+
"id": id_name,
|
| 680 |
+
"level": level,
|
| 681 |
+
"question": question,
|
| 682 |
+
"solution": solution
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
# Add to the list of problems
|
| 686 |
+
all_problems.append(problem_entry)
|
| 687 |
+
|
| 688 |
+
# Shuffle the problems to mix templates and levels
|
| 689 |
+
random.shuffle(all_problems)
|
| 690 |
+
|
| 691 |
+
# Write all problems to a .jsonl file
|
| 692 |
+
with open(output_file, "w") as file:
|
| 693 |
+
for problem in all_problems:
|
| 694 |
+
file.write(json.dumps(problem))
|
| 695 |
+
file.write("\n")
|
| 696 |
+
|
| 697 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 698 |
+
|
| 699 |
+
|
| 700 |
+
if __name__ == "__main__":
|
| 701 |
+
main()
|
data/templates/branches/chemical_engineering/transport_phenomena/shell_momentum_balances.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
from data.templates.branches.chemical_engineering.constants import COMMON_LIQUIDS, GRAVITATIONAL_ACCELERATION
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# Template 1 (Easy)
|
| 7 |
+
def template_falling_film_max_velocity():
|
| 8 |
+
"""
|
| 9 |
+
Falling Film Maximum Velocity Calculation
|
| 10 |
+
|
| 11 |
+
Scenario:
|
| 12 |
+
This template calculates the maximum velocity of a liquid film flowing
|
| 13 |
+
down an inclined plane under gravity. The maximum velocity occurs at the
|
| 14 |
+
free surface (the liquid-air interface), where shear stress is zero.
|
| 15 |
+
The calculation is based on the final velocity profile equation derived
|
| 16 |
+
from a shell momentum balance for this system.
|
| 17 |
+
|
| 18 |
+
Core Equation:
|
| 19 |
+
v_z_max = (rho * g * delta^2 * cos(beta)) / (2 * mu)
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
tuple: A tuple containing:
|
| 23 |
+
- str: A question asking to compute the maximum film velocity.
|
| 24 |
+
- str: A step-by-step solution showing the calculation.
|
| 25 |
+
"""
|
| 26 |
+
# 1. Parameterize the inputs with random values
|
| 27 |
+
# Choose a random fluid name (key) from the dictionary
|
| 28 |
+
fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
|
| 29 |
+
# Get the corresponding properties (the tuple of density and viscosity)
|
| 30 |
+
fluid_density, fluid_viscosity = COMMON_LIQUIDS[fluid_name]
|
| 31 |
+
|
| 32 |
+
# Film thickness in mm
|
| 33 |
+
film_thickness_mm = round(random.uniform(0.5, 5.0), 2)
|
| 34 |
+
|
| 35 |
+
# Angle of inclination from the vertical in degrees
|
| 36 |
+
inclination_angle_deg = random.randint(5, 85)
|
| 37 |
+
|
| 38 |
+
# Constants
|
| 39 |
+
g = GRAVITATIONAL_ACCELERATION
|
| 40 |
+
|
| 41 |
+
# 2. Perform unit conversions and core calculation
|
| 42 |
+
# Convert film thickness from mm to m
|
| 43 |
+
film_thickness_m = film_thickness_mm / 1000.0
|
| 44 |
+
|
| 45 |
+
# Convert inclination angle from degrees to radians for math functions
|
| 46 |
+
inclination_angle_rad = math.radians(inclination_angle_deg)
|
| 47 |
+
|
| 48 |
+
# Calculate the maximum velocity
|
| 49 |
+
v_max = (fluid_density * g * (film_thickness_m**2) * math.cos(inclination_angle_rad)) / (2 * fluid_viscosity)
|
| 50 |
+
|
| 51 |
+
# 3. Generate the question and solution strings
|
| 52 |
+
question = (
|
| 53 |
+
f"A thin film of {fluid_name} is flowing down a flat surface inclined at an "
|
| 54 |
+
f"angle of {inclination_angle_deg} degrees from the vertical. The film thickness is "
|
| 55 |
+
f"measured to be {film_thickness_mm} mm.\n\n"
|
| 56 |
+
f"Given the fluid's density is {fluid_density} kg/m^3 and its viscosity is "
|
| 57 |
+
f"{fluid_viscosity} Pa·s, calculate the maximum velocity of the film (at the "
|
| 58 |
+
f"liquid-air interface). Assume the flow is laminar.\n\n"
|
| 59 |
+
f"Use a value of g = {g} m/s^2."
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
solution = (
|
| 63 |
+
f"**Step 1:** Identify Given Information\n"
|
| 64 |
+
f"First, we list the known values from the problem statement.\n"
|
| 65 |
+
f"- Fluid: {fluid_name}\n"
|
| 66 |
+
f"- Fluid Density (rho): {fluid_density} kg/m^3\n"
|
| 67 |
+
f"- Fluid Viscosity (mu): {fluid_viscosity} Pa·s\n"
|
| 68 |
+
f"- Film Thickness (delta): {film_thickness_mm} mm\n"
|
| 69 |
+
f"- Inclination Angle (beta): {inclination_angle_deg} degrees\n"
|
| 70 |
+
f"- Gravitational Acceleration (g): {g} m/s^2\n\n"
|
| 71 |
+
|
| 72 |
+
f"**Step 2:** Perform Unit Conversions\n"
|
| 73 |
+
f"The core equation requires all units to be in the SI base system. We must convert the film thickness from millimeters to meters.\n"
|
| 74 |
+
f"delta = {film_thickness_mm} mm * (1 m / 1000 mm) = {film_thickness_m} m\n\n"
|
| 75 |
+
|
| 76 |
+
f"**Step 3:** State the Core Equation\n"
|
| 77 |
+
f"For a laminar falling film, the maximum velocity (v_z_max) at the liquid-air interface is given by the equation derived from the shell momentum balance:\n"
|
| 78 |
+
f"v_z_max = (rho * g * delta^2 * cos(beta)) / (2 * mu)\n\n"
|
| 79 |
+
|
| 80 |
+
f"**Step 4:** Substitute Values into the Equation\n"
|
| 81 |
+
f"Now, we substitute our known values (with correct units) into the equation.\n"
|
| 82 |
+
f"v_z_max = (({fluid_density} kg/m^3) * ({g} m/s^2) * ({film_thickness_m} m)^2 * cos({inclination_angle_deg} deg)) / (2 * ({fluid_viscosity} Pa·s))\n\n"
|
| 83 |
+
|
| 84 |
+
f"**Step 5:** Calculate the Final Velocity\n"
|
| 85 |
+
f"Performing the calculation gives the maximum velocity.\n"
|
| 86 |
+
f"v_z_max = {round(v_max, 5)} m/s\n\n"
|
| 87 |
+
|
| 88 |
+
f"**Answer:** The maximum velocity of the {fluid_name} film is {round(v_max, 5)} m/s."
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
return question, solution
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
# Template 2 (Easy)
|
| 95 |
+
def template_hagen_poiseuille_flowrate():
|
| 96 |
+
"""
|
| 97 |
+
Hagen-Poiseuille Volumetric Flow Rate Calculation
|
| 98 |
+
|
| 99 |
+
Scenario:
|
| 100 |
+
This template calculates the volumetric flow rate (Q) for a fluid
|
| 101 |
+
experiencing laminar flow through a straight, circular pipe of
|
| 102 |
+
constant cross-section. It's based on the exact solution for
|
| 103 |
+
pressure-driven flow derived from a shell momentum balance.
|
| 104 |
+
|
| 105 |
+
Core Equation (Hagen-Poiseuille Equation):
|
| 106 |
+
Q = (pi * (P0 - PL) * R^4) / (8 * mu * L)
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
tuple: A tuple containing:
|
| 110 |
+
- str: A question asking to compute the volumetric flow rate.
|
| 111 |
+
- str: A step-by-step solution showing the calculation.
|
| 112 |
+
"""
|
| 113 |
+
# 1. Parameterize the inputs with random values
|
| 114 |
+
# Choose a random fluid name (key) from the dictionary
|
| 115 |
+
fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
|
| 116 |
+
# Get the corresponding properties (the tuple of density and viscosity)
|
| 117 |
+
fluid_density, fluid_viscosity_Pas = COMMON_LIQUIDS[fluid_name]
|
| 118 |
+
|
| 119 |
+
# Pipe radius in cm
|
| 120 |
+
pipe_radius_cm = round(random.uniform(0.5, 5.0), 2)
|
| 121 |
+
# Pipe length in m
|
| 122 |
+
pipe_length_m = round(random.uniform(5.0, 100.0), 1)
|
| 123 |
+
# Pressure drop in kPa
|
| 124 |
+
pressure_drop_kPa = random.randint(50, 500)
|
| 125 |
+
# Convert viscosity to cP for the problem statement (1 Pa·s = 1000 cP)
|
| 126 |
+
fluid_viscosity_cP = fluid_viscosity_Pas * 1000
|
| 127 |
+
|
| 128 |
+
# 2. Perform unit conversions and core calculation
|
| 129 |
+
# Convert radius from cm to m
|
| 130 |
+
pipe_radius_m = pipe_radius_cm / 100.0
|
| 131 |
+
# Convert pressure drop from kPa to Pa
|
| 132 |
+
pressure_drop_Pa = pressure_drop_kPa * 1000
|
| 133 |
+
|
| 134 |
+
# Calculate the volumetric flow rate (Q)
|
| 135 |
+
# Q = (pi * delta_P * R^4) / (8 * mu * L)
|
| 136 |
+
q_flow_rate = (math.pi * pressure_drop_Pa * (pipe_radius_m**4)) / (8 * fluid_viscosity_Pas * pipe_length_m)
|
| 137 |
+
|
| 138 |
+
# 3. Generate the question and solution strings
|
| 139 |
+
question = (
|
| 140 |
+
f"A fluid, {fluid_name}, is flowing through a smooth circular pipe. "
|
| 141 |
+
f"The pipe has an inner radius of {pipe_radius_cm} cm and a total length of {pipe_length_m} m. "
|
| 142 |
+
f"The pressure drop across the length of the pipe is measured to be {pressure_drop_kPa} kPa.\n\n"
|
| 143 |
+
f"Given that the viscosity of {fluid_name} is approximately {fluid_viscosity_cP:.2f} cP, "
|
| 144 |
+
f"calculate the volumetric flow rate (Q) in m^3/s. Assume the flow is laminar."
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
solution = (
|
| 148 |
+
f"**Step 1:** Identify Given Information\n"
|
| 149 |
+
f"First, we list the known values from the problem statement.\n"
|
| 150 |
+
f"- Fluid: {fluid_name}\n"
|
| 151 |
+
f"- Pipe Radius (R): {pipe_radius_cm} cm\n"
|
| 152 |
+
f"- Pipe Length (L): {pipe_length_m} m\n"
|
| 153 |
+
f"- Pressure Drop (P0 - PL): {pressure_drop_kPa} kPa\n"
|
| 154 |
+
f"- Fluid Viscosity (mu): {fluid_viscosity_cP:.2f} cP\n\n"
|
| 155 |
+
|
| 156 |
+
f"**Step 2:** Perform Unit Conversions\n"
|
| 157 |
+
f"The Hagen-Poiseuille equation requires all units to be in the SI base system. We must convert the radius, pressure drop, and viscosity.\n"
|
| 158 |
+
f"- Radius: R = {pipe_radius_cm} cm * (1 m / 100 cm) = {pipe_radius_m} m\n"
|
| 159 |
+
f"- Pressure Drop: P0 - PL = {pressure_drop_kPa} kPa * (1000 Pa / 1 kPa) = {pressure_drop_Pa} Pa\n"
|
| 160 |
+
f"- Viscosity: mu = {fluid_viscosity_cP:.2f} cP * (1 Pa·s / 1000 cP) = {fluid_viscosity_Pas} Pa·s\n\n"
|
| 161 |
+
|
| 162 |
+
f"**Step 3:** State the Core Equation\n"
|
| 163 |
+
f"The volumetric flow rate (Q) for laminar flow in a circular pipe is given by the Hagen-Poiseuille equation:\n"
|
| 164 |
+
f"Q = (pi * (P0 - PL) * R^4) / (8 * mu * L)\n\n"
|
| 165 |
+
|
| 166 |
+
f"**Step 4:** Substitute Values into the Equation\n"
|
| 167 |
+
f"Now, we substitute the converted SI values into the equation.\n"
|
| 168 |
+
f"Q = (pi * ({pressure_drop_Pa} Pa) * ({pipe_radius_m} m)^4) / (8 * ({fluid_viscosity_Pas} Pa·s) * ({pipe_length_m} m))\n\n"
|
| 169 |
+
|
| 170 |
+
f"**Step 5:** Calculate the Final Flow Rate\n"
|
| 171 |
+
f"Performing the calculation gives the volumetric flow rate.\n"
|
| 172 |
+
f"Q = {q_flow_rate:.6f} m^3/s\n\n"
|
| 173 |
+
|
| 174 |
+
f"**Answer:** The volumetric flow rate of {fluid_name} through the pipe is {q_flow_rate:.6f} m^3/s."
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
return question, solution
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
# Template 3 (Intermediate)
|
| 181 |
+
def template_annulus_flowrate():
|
| 182 |
+
"""
|
| 183 |
+
Volumetric Flow Rate in an Annulus Calculation
|
| 184 |
+
|
| 185 |
+
Scenario:
|
| 186 |
+
This template calculates the volumetric flow rate (Q) for a fluid in
|
| 187 |
+
laminar flow through the concentric cylindrical region between two pipes (an annulus).
|
| 188 |
+
The equation is derived from the shell momentum balance for this specific geometry.
|
| 189 |
+
|
| 190 |
+
Core Equation:
|
| 191 |
+
Q = (pi*(P0-PL)*R^4)/(8*mu*L) * [(1-kappa^4) - ((1-kappa^2)^2 / ln(1/kappa))]
|
| 192 |
+
|
| 193 |
+
Returns:
|
| 194 |
+
tuple: A tuple containing:
|
| 195 |
+
- str: A question asking to compute the volumetric flow rate in an annulus.
|
| 196 |
+
- str: A step-by-step solution showing the calculation.
|
| 197 |
+
"""
|
| 198 |
+
# 1. Parameterize the inputs with random values
|
| 199 |
+
# Choose a random fluid name (key) from the dictionary
|
| 200 |
+
fluid_name = random.choice(list(COMMON_LIQUIDS.keys()))
|
| 201 |
+
# Get the corresponding properties (the tuple of density and viscosity)
|
| 202 |
+
fluid_density, fluid_viscosity_Pas = COMMON_LIQUIDS[fluid_name]
|
| 203 |
+
|
| 204 |
+
# Define radii and ensure inner radius is smaller than outer radius
|
| 205 |
+
outer_radius_cm = round(random.uniform(2.0, 10.0), 2)
|
| 206 |
+
kappa = round(random.uniform(0.2, 0.8), 2) # Ratio of inner to outer radius
|
| 207 |
+
inner_radius_cm = round(kappa * outer_radius_cm, 2)
|
| 208 |
+
|
| 209 |
+
# Pipe length in m
|
| 210 |
+
pipe_length_m = round(random.uniform(10.0, 150.0), 1)
|
| 211 |
+
# Pressure drop in kPa
|
| 212 |
+
pressure_drop_kPa = random.randint(100, 1000)
|
| 213 |
+
|
| 214 |
+
# 2. Perform unit conversions and core calculation
|
| 215 |
+
# Convert radii from cm to m
|
| 216 |
+
outer_radius_m = outer_radius_cm / 100.0
|
| 217 |
+
inner_radius_m = inner_radius_cm / 100.0
|
| 218 |
+
|
| 219 |
+
# Convert pressure drop from kPa to Pa
|
| 220 |
+
pressure_drop_Pa = pressure_drop_kPa * 1000
|
| 221 |
+
|
| 222 |
+
# The dimensionless ratio, kappa
|
| 223 |
+
kappa_val = inner_radius_m / outer_radius_m
|
| 224 |
+
|
| 225 |
+
# Calculate the flow rate using the annulus equation
|
| 226 |
+
term1 = (math.pi * pressure_drop_Pa * (outer_radius_m**4)) / (8 * fluid_viscosity_Pas * pipe_length_m)
|
| 227 |
+
shape_factor = (1 - kappa_val**4) - (((1 - kappa_val**2)**2) / math.log(1 / kappa_val))
|
| 228 |
+
q_flow_rate = term1 * shape_factor
|
| 229 |
+
|
| 230 |
+
# 3. Generate the question and solution strings
|
| 231 |
+
question = (
|
| 232 |
+
f"Laminar flow of {fluid_name} occurs in the annular space between two concentric pipes. "
|
| 233 |
+
f"The inner pipe has an outer radius of {inner_radius_cm} cm, and the outer pipe has an inner radius of {outer_radius_cm} cm. "
|
| 234 |
+
f"The concentric pipes have a length of {pipe_length_m} m.\n\n"
|
| 235 |
+
f"A pressure drop of {pressure_drop_kPa} kPa is maintained over the length of the pipes. "
|
| 236 |
+
f"The fluid viscosity is {fluid_viscosity_Pas} Pa·s.\n\n"
|
| 237 |
+
f"Calculate the volumetric flow rate (Q) through the annular space in m^3/s."
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
solution = (
|
| 241 |
+
f"**Step 1:** Identify Given Information\n"
|
| 242 |
+
f"First, we list the known values from the problem statement.\n"
|
| 243 |
+
f"- Fluid: {fluid_name}\n"
|
| 244 |
+
f"- Inner Radius (R_inner): {inner_radius_cm} cm\n"
|
| 245 |
+
f"- Outer Radius (R_outer): {outer_radius_cm} cm\n"
|
| 246 |
+
f"- Pipe Length (L): {pipe_length_m} m\n"
|
| 247 |
+
f"- Pressure Drop (P0 - PL): {pressure_drop_kPa} kPa\n"
|
| 248 |
+
f"- Fluid Viscosity (mu): {fluid_viscosity_Pas} Pa·s\n\n"
|
| 249 |
+
|
| 250 |
+
f"**Step 2:** Perform Unit Conversions\n"
|
| 251 |
+
f"The equation requires all units to be in the SI base system. We must convert the radii and the pressure drop.\n"
|
| 252 |
+
f"- Inner Radius: R_inner = {inner_radius_cm} cm * (1 m / 100 cm) = {inner_radius_m} m\n"
|
| 253 |
+
f"- Outer Radius: R_outer = {outer_radius_cm} cm * (1 m / 100 cm) = {outer_radius_m} m\n"
|
| 254 |
+
f"- Pressure Drop: P0 - PL = {pressure_drop_kPa} kPa * (1000 Pa / 1 kPa) = {pressure_drop_Pa} Pa\n\n"
|
| 255 |
+
|
| 256 |
+
f"**Step 3:** Calculate the Dimensionless Ratio (kappa)\n"
|
| 257 |
+
f"Kappa (k) is the ratio of the inner radius to the outer radius.\n"
|
| 258 |
+
f"kappa = R_inner / R_outer = {inner_radius_m} m / {outer_radius_m} m = {kappa_val:.3f}\n\n"
|
| 259 |
+
|
| 260 |
+
f"**Step 4:** State the Core Equation\n"
|
| 261 |
+
f"The volumetric flow rate (Q) for laminar flow in an annulus is:\n"
|
| 262 |
+
f"Q = (pi * (P0 - PL) * R_outer^4) / (8 * mu * L) * [ (1 - kappa^4) - ((1 - kappa^2)^2 / ln(1/kappa)) ]\n\n"
|
| 263 |
+
|
| 264 |
+
f"**Step 5:** Substitute Values and Calculate\n"
|
| 265 |
+
f"We substitute the converted SI values into the equation. Let's calculate the main term and the shape factor separately for clarity.\n"
|
| 266 |
+
f"Main Term = (pi * {pressure_drop_Pa} Pa * ({outer_radius_m} m)^4) / (8 * {fluid_viscosity_Pas} Pa·s * {pipe_length_m} m) = {term1:.6f}\n"
|
| 267 |
+
f"Shape Factor = [ (1 - {kappa_val:.3f}^4) - ((1 - {kappa_val:.3f}^2)^2 / ln(1/{kappa_val:.3f})) ] = {shape_factor:.4f}\n\n"
|
| 268 |
+
f"Q = Main Term * Shape Factor\n"
|
| 269 |
+
f"Q = {term1:.6f} * {shape_factor:.4f} = {q_flow_rate:.7f} m^3/s\n\n"
|
| 270 |
+
|
| 271 |
+
f"Answer: The volumetric flow rate of {fluid_name} through the annular space is {q_flow_rate:.7f} m^3/s."
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
return question, solution
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def main():
|
| 278 |
+
"""
|
| 279 |
+
Generate numerous instances of each shell momentum balances and velocity distributions in
|
| 280 |
+
laminar flow template with different random seeds and write the results to a JSONL file.
|
| 281 |
+
"""
|
| 282 |
+
import json
|
| 283 |
+
import os
|
| 284 |
+
|
| 285 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 286 |
+
output_file = "testset/chemical_engineering/transport_phenomena/shell_momentum_balances.jsonl"
|
| 287 |
+
|
| 288 |
+
# Create the directory if it doesn't exist
|
| 289 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 290 |
+
|
| 291 |
+
# List of template functions with their ID and level
|
| 292 |
+
templates = [
|
| 293 |
+
(template_falling_film_max_velocity, "falling_film_max_velocity", "Easy"),
|
| 294 |
+
(template_hagen_poiseuille_flowrate, "hagen_poiseuille_flowrate", "Easy"),
|
| 295 |
+
(template_annulus_flowrate, "annulus_flowrate", "Intermediate"),
|
| 296 |
+
]
|
| 297 |
+
|
| 298 |
+
# List to store all generated problems
|
| 299 |
+
all_problems = []
|
| 300 |
+
|
| 301 |
+
# Generate problems for each template
|
| 302 |
+
for template_func, id_name, level in templates:
|
| 303 |
+
for _ in range(50):
|
| 304 |
+
# Generate a unique seed for each problem
|
| 305 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 306 |
+
random.seed(seed)
|
| 307 |
+
|
| 308 |
+
# Generate the problem and solution
|
| 309 |
+
question, solution = template_func()
|
| 310 |
+
|
| 311 |
+
# Create a JSON entry
|
| 312 |
+
problem_entry = {
|
| 313 |
+
"seed": seed,
|
| 314 |
+
"branch": "chemical_engineering",
|
| 315 |
+
"domain": "transport_phenomena",
|
| 316 |
+
"area": "shell_momentum_balances",
|
| 317 |
+
"id": id_name,
|
| 318 |
+
"level": level,
|
| 319 |
+
"question": question,
|
| 320 |
+
"solution": solution
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
# Add to the list of problems
|
| 324 |
+
all_problems.append(problem_entry)
|
| 325 |
+
|
| 326 |
+
# Shuffle the problems to mix templates and levels
|
| 327 |
+
random.shuffle(all_problems)
|
| 328 |
+
|
| 329 |
+
# Write all problems to a .jsonl file
|
| 330 |
+
with open(output_file, "w") as file:
|
| 331 |
+
for problem in all_problems:
|
| 332 |
+
file.write(json.dumps(problem))
|
| 333 |
+
file.write("\n")
|
| 334 |
+
|
| 335 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
if __name__ == "__main__":
|
| 339 |
+
main()
|
data/templates/branches/chemical_engineering/transport_phenomena/viscosity_and_momentum_transport.py
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
from data.templates.branches.chemical_engineering.constants import COMMON_LIQUIDS, COMMON_GASES, GAS_MOLECULAR_PARAMS, POWER_LAW_FLUIDS
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# Template 1 (Easy)
|
| 7 |
+
def template_newtons_law_shear_stress():
|
| 8 |
+
"""
|
| 9 |
+
Shear Stress and Force Calculation for Flow Between Parallel Plates
|
| 10 |
+
|
| 11 |
+
Scenario:
|
| 12 |
+
This template models Couette flow, a classic fluid dynamics problem where a layer
|
| 13 |
+
of fluid is sheared between two parallel plates. One plate is stationary, and
|
| 14 |
+
the other moves at a constant velocity. Assuming a linear velocity profile,
|
| 15 |
+
we use Newton's Law of Viscosity to find the shear stress in the fluid and
|
| 16 |
+
the force required to move the plate.
|
| 17 |
+
|
| 18 |
+
The relevant equations are:
|
| 19 |
+
- Velocity Gradient: dvx/dy = V / Y
|
| 20 |
+
- Shear Stress: tau_yx = mu * (dvx/dy)
|
| 21 |
+
- Force: F = tau_yx * A
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
tuple: A tuple containing:
|
| 25 |
+
- str: A question asking to compute shear stress and force.
|
| 26 |
+
- str: A step-by-step solution showing the calculations.
|
| 27 |
+
"""
|
| 28 |
+
# 1. Parameterize the inputs with random values
|
| 29 |
+
fluid_name, (density, viscosity) = random.choice(list(COMMON_LIQUIDS.items()))
|
| 30 |
+
V = round(random.uniform(0.1, 2.0), 2)
|
| 31 |
+
Y_cm = round(random.uniform(0.1, 2.5), 2)
|
| 32 |
+
Y_m = Y_cm / 100
|
| 33 |
+
A = round(random.uniform(0.5, 5.0), 2)
|
| 34 |
+
|
| 35 |
+
# 2. Perform the core calculation
|
| 36 |
+
velocity_gradient = V / Y_m
|
| 37 |
+
shear_stress = viscosity * velocity_gradient
|
| 38 |
+
force = shear_stress * A
|
| 39 |
+
|
| 40 |
+
# 3. Generate the question and solution strings
|
| 41 |
+
question = (
|
| 42 |
+
f"Two large parallel plates with an area of {A} m^2 each are separated by a "
|
| 43 |
+
f"thin film of {fluid_name} that is {Y_cm} cm thick. The top plate is moved "
|
| 44 |
+
f"at a constant velocity of {V} m/s, while the bottom plate is held stationary.\n\n"
|
| 45 |
+
f"Assuming the fluid exhibits Newtonian behavior and a linear velocity profile, calculate:\n"
|
| 46 |
+
f"a) The shear stress (tau_yx) exerted on the fluid.\n"
|
| 47 |
+
f"b) The total force (F) required to move the top plate at the given velocity."
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
solution = (
|
| 51 |
+
f"**Given Information:**\n"
|
| 52 |
+
f"- Fluid: {fluid_name}\n"
|
| 53 |
+
f"- Dynamic Viscosity of {fluid_name} (mu): {viscosity:.2e} Pa·s\n"
|
| 54 |
+
f"- Plate Area (A): {A} m^2\n"
|
| 55 |
+
f"- Plate Velocity (V): {V} m/s\n"
|
| 56 |
+
f"- Distance between plates (Y): {Y_cm} cm = {Y_m} m\n\n"
|
| 57 |
+
|
| 58 |
+
f"**Step 1:** Calculate the velocity gradient (dvx/dy).\n"
|
| 59 |
+
f"For a linear velocity profile between a stationary and a moving plate, the gradient is constant:\n"
|
| 60 |
+
f"dvx/dy = V / Y\n"
|
| 61 |
+
f"dvx/dy = {V} m/s / {Y_m} m = {velocity_gradient:.2f} 1/s\n\n"
|
| 62 |
+
|
| 63 |
+
f"**Step 2:** Calculate the shear stress (tau_yx).\n"
|
| 64 |
+
f"Using Newton's Law of Viscosity:\n"
|
| 65 |
+
f"tau_yx = mu * (dvx/dy)\n"
|
| 66 |
+
f"tau_yx = ({viscosity:.2e} Pa·s) * ({velocity_gradient:.2f} 1/s)\n"
|
| 67 |
+
f"tau_yx = {shear_stress:.3f} Pa\n\n"
|
| 68 |
+
|
| 69 |
+
f"**Step 3:** Calculate the force (F).\n"
|
| 70 |
+
f"Force is the shear stress acting over the entire area of the plate:\n"
|
| 71 |
+
f"F = tau_yx * A\n"
|
| 72 |
+
f"F = ({shear_stress:.3f} Pa) * ({A} m^2)\n"
|
| 73 |
+
f"F = {force:.3f} N\n\n"
|
| 74 |
+
|
| 75 |
+
f"**Answer:**\n"
|
| 76 |
+
f"a) The shear stress in the fluid is **{shear_stress:.3f} Pa**.\n"
|
| 77 |
+
f"b) The force required to move the plate is **{force:.3f} N**."
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
return question, solution
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# Template 2 (Easy)
|
| 84 |
+
def template_kinematic_viscosity():
|
| 85 |
+
"""
|
| 86 |
+
Kinematic Viscosity Calculation
|
| 87 |
+
|
| 88 |
+
Scenario:
|
| 89 |
+
This template tests the definition of kinematic viscosity (ν), which is the
|
| 90 |
+
ratio of the dynamic viscosity (μ) to the density (ρ) of a fluid. It is a
|
| 91 |
+
measure of a fluid's internal resistance to flow under gravitational forces.
|
| 92 |
+
|
| 93 |
+
The relevant equations are:
|
| 94 |
+
- Kinematic Viscosity: ν = μ / ρ
|
| 95 |
+
- Unit Conversion: 1 m²/s = 10,000 Stokes (St)
|
| 96 |
+
|
| 97 |
+
Returns:
|
| 98 |
+
tuple: A tuple containing:
|
| 99 |
+
- str: A question asking to compute the kinematic viscosity.
|
| 100 |
+
- str: A step-by-step solution showing the calculation and unit conversion.
|
| 101 |
+
"""
|
| 102 |
+
# 1. Parameterize the inputs with random values
|
| 103 |
+
|
| 104 |
+
# Combine liquids and gases into one dictionary for random selection
|
| 105 |
+
all_fluids = {**COMMON_LIQUIDS, **COMMON_GASES}
|
| 106 |
+
fluid_name, (density, viscosity) = random.choice(list(all_fluids.items()))
|
| 107 |
+
|
| 108 |
+
# Randomly choose the target units for the answer
|
| 109 |
+
target_units = random.choice(["m^2/s", "Stokes (St)"])
|
| 110 |
+
|
| 111 |
+
# 2. Perform the core calculation
|
| 112 |
+
|
| 113 |
+
# Calculate kinematic viscosity in SI units (m²/s)
|
| 114 |
+
nu_si = viscosity / density
|
| 115 |
+
|
| 116 |
+
# Perform unit conversion if necessary
|
| 117 |
+
final_nu = nu_si
|
| 118 |
+
if "Stokes" in target_units:
|
| 119 |
+
final_nu = nu_si * 10000
|
| 120 |
+
|
| 121 |
+
# 3. Generate the question and solution strings
|
| 122 |
+
|
| 123 |
+
question = (
|
| 124 |
+
f"The dynamic viscosity (μ) of {fluid_name} at standard conditions is approximately "
|
| 125 |
+
f"{viscosity:.2e} Pa·s, and its density (ρ) is {density:.3f} kg/m³.\n\n"
|
| 126 |
+
f"Calculate the kinematic viscosity (ν) of {fluid_name} in units of **{target_units}**."
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
solution = (
|
| 130 |
+
f"**Given Information:**\n"
|
| 131 |
+
f"- Fluid: {fluid_name}\n"
|
| 132 |
+
f"- Dynamic Viscosity (μ): {viscosity:.2e} Pa·s\n"
|
| 133 |
+
f"- Density (ρ): {density:.3f} kg/m³\n\n"
|
| 134 |
+
|
| 135 |
+
f"**Step 1:** State the formula for kinematic viscosity.\n"
|
| 136 |
+
f"Kinematic viscosity (ν) is defined as the ratio of dynamic viscosity to density:\n"
|
| 137 |
+
f"ν = μ / ρ\n\n"
|
| 138 |
+
|
| 139 |
+
f"**Step 2:** Calculate the kinematic viscosity in SI units (m²/s).\n"
|
| 140 |
+
f"Substitute the given values into the formula:\n"
|
| 141 |
+
f"ν = ({viscosity:.2e} Pa·s) / ({density:.3f} kg/m³)\n"
|
| 142 |
+
f"ν = {nu_si:.3e} m²/s\n\n"
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
# Add the unit conversion step only if needed
|
| 146 |
+
if "Stokes" in target_units:
|
| 147 |
+
solution += (
|
| 148 |
+
f"**Step 3:** Convert the result to Stokes (St).\n"
|
| 149 |
+
f"The conversion factor is 1 m²/s = 10,000 St.\n"
|
| 150 |
+
f"ν = ({nu_si:.3e} m²/s) * (10,000 St / 1 m²/s)\n"
|
| 151 |
+
f"ν = {final_nu:.4f} St\n\n"
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
solution += (
|
| 155 |
+
f"**Answer:**\n"
|
| 156 |
+
f"The kinematic viscosity of {fluid_name} is **{final_nu:.4f} {target_units}**."
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
return question, solution
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
# Template 3 (Intermediate)
|
| 163 |
+
def template_gas_viscosity_kinetic_theory():
|
| 164 |
+
"""
|
| 165 |
+
Gas Viscosity Estimation from Kinetic Theory
|
| 166 |
+
|
| 167 |
+
Scenario:
|
| 168 |
+
This template uses the Chapman-Enskog equation, derived from the kinetic
|
| 169 |
+
theory of gases, to estimate the dynamic viscosity (μ) of a low-density gas.
|
| 170 |
+
This model connects a macroscopic property (viscosity) to molecular
|
| 171 |
+
properties (molar mass, size).
|
| 172 |
+
|
| 173 |
+
The relevant equation is:
|
| 174 |
+
μ = (2.6693e-6 * sqrt(M*T)) / (σ² * Ω_μ)
|
| 175 |
+
|
| 176 |
+
Where:
|
| 177 |
+
- μ = viscosity in Pa·s
|
| 178 |
+
- M = Molar Mass in g/mol
|
| 179 |
+
- T = Absolute Temperature in K
|
| 180 |
+
- σ = Lennard-Jones molecular diameter in Angstroms (Å)
|
| 181 |
+
- Ω_μ = Collision integral (dimensionless)
|
| 182 |
+
|
| 183 |
+
Returns:
|
| 184 |
+
tuple: A tuple containing:
|
| 185 |
+
- str: A question asking to estimate the gas viscosity.
|
| 186 |
+
- str: A step-by-step solution showing the calculation.
|
| 187 |
+
"""
|
| 188 |
+
# 1. Parameterize the inputs with random values
|
| 189 |
+
gas_name, (molar_mass, sigma, epsilon) = random.choice(list(GAS_MOLECULAR_PARAMS.items()))
|
| 190 |
+
|
| 191 |
+
# Temperature in Kelvin
|
| 192 |
+
temperature_K = random.randint(250, 600)
|
| 193 |
+
|
| 194 |
+
# Collision integral (dimensionless), kept close to 1.0 for simplicity
|
| 195 |
+
omega_mu = round(random.uniform(0.95, 1.05), 3)
|
| 196 |
+
|
| 197 |
+
# 2. Perform the core calculation
|
| 198 |
+
|
| 199 |
+
# Numerator of the Chapman-Enskog equation
|
| 200 |
+
numerator = 2.6693e-6 * math.sqrt(molar_mass * temperature_K)
|
| 201 |
+
|
| 202 |
+
# Denominator of the Chapman-Enskog equation
|
| 203 |
+
denominator = (sigma**2) * omega_mu
|
| 204 |
+
|
| 205 |
+
# Final viscosity calculation
|
| 206 |
+
viscosity = numerator / denominator
|
| 207 |
+
|
| 208 |
+
# 3. Generate the question and solution strings
|
| 209 |
+
|
| 210 |
+
question = (
|
| 211 |
+
f"Estimate the dynamic viscosity (μ) of {gas_name} gas at a temperature of {temperature_K} K.\n\n"
|
| 212 |
+
f"Use the Chapman-Enskog equation for low-density gases:\n"
|
| 213 |
+
f"μ = (2.6693e-6 * sqrt(M*T)) / (σ^2 * Ω_μ)\n\n"
|
| 214 |
+
f"You are given the following parameters for {gas_name}:\n"
|
| 215 |
+
f"- Molar Mass (M) = {molar_mass} g/mol\n"
|
| 216 |
+
f"- Lennard-Jones diameter (σ) = {sigma} Å\n"
|
| 217 |
+
f"- Collision Integral (Ω_μ) = {omega_mu}\n\n"
|
| 218 |
+
f"Provide your answer in units of Pascal-seconds (Pa·s)."
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
solution = (
|
| 222 |
+
f"**Given Information:**\n"
|
| 223 |
+
f"- Gas: {gas_name}\n"
|
| 224 |
+
f"- Temperature (T): {temperature_K} K\n"
|
| 225 |
+
f"- Molar Mass (M): {molar_mass} g/mol\n"
|
| 226 |
+
f"- Lennard-Jones diameter (σ): {sigma} Å\n"
|
| 227 |
+
f"- Collision Integral (Ω_μ): {omega_mu}\n\n"
|
| 228 |
+
|
| 229 |
+
f"**Step 1:** State the Chapman-Enskog equation.\n"
|
| 230 |
+
f"The formula for estimating the viscosity of a low-density gas is:\n"
|
| 231 |
+
f"μ = (2.6693e-6 * sqrt(M*T)) / (σ^2 * Ω_μ)\n\n"
|
| 232 |
+
|
| 233 |
+
f"**Step 2:** Substitute the given values into the equation.\n"
|
| 234 |
+
f"μ = (2.6693e-6 * sqrt({molar_mass} * {temperature_K})) / (({sigma})^2 * {omega_mu})\n\n"
|
| 235 |
+
|
| 236 |
+
f"**Step 3:** Calculate the numerator and denominator.\n"
|
| 237 |
+
f"- Numerator = 2.6693e-6 * sqrt({molar_mass * temperature_K:.2f}) = {numerator:.4e}\n"
|
| 238 |
+
f"- Denominator = {sigma**2:.3f} * {omega_mu} = {denominator:.3f}\n\n"
|
| 239 |
+
|
| 240 |
+
f"**Step 4:** Calculate the final viscosity.\n"
|
| 241 |
+
f"μ = {numerator:.4e} / {denominator:.3f}\n"
|
| 242 |
+
f"μ = {viscosity:.3e} Pa·s\n\n"
|
| 243 |
+
|
| 244 |
+
f"**Answer:**\n"
|
| 245 |
+
f"The estimated dynamic viscosity of {gas_name} at {temperature_K} K is **{viscosity:.3e} Pa·s**."
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
return question, solution
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
# Template 4 (Intermediate)
|
| 252 |
+
def template_reynolds_number_flow_regime():
|
| 253 |
+
"""
|
| 254 |
+
Reynolds Number and Flow Regime Calculation
|
| 255 |
+
|
| 256 |
+
Scenario:
|
| 257 |
+
This template introduces the Reynolds number (Re), the dimensionless ratio of
|
| 258 |
+
inertial forces to viscous forces in a fluid. Its value is critical for
|
| 259 |
+
predicting whether a flow is smooth (laminar) or chaotic (turbulent).
|
| 260 |
+
The calculation depends on a characteristic length, which varies with geometry.
|
| 261 |
+
|
| 262 |
+
The relevant equation is:
|
| 263 |
+
Re = (ρ * v * L) / μ
|
| 264 |
+
|
| 265 |
+
Where:
|
| 266 |
+
- ρ = density
|
| 267 |
+
- v = average velocity
|
| 268 |
+
- L = characteristic length (e.g., pipe diameter or plate length)
|
| 269 |
+
- μ = dynamic viscosity
|
| 270 |
+
|
| 271 |
+
Returns:
|
| 272 |
+
tuple: A tuple containing:
|
| 273 |
+
- str: A question asking to compute the Reynolds number and find the flow regime.
|
| 274 |
+
- str: A step-by-step solution.
|
| 275 |
+
"""
|
| 276 |
+
# 1. Parameterize the inputs with random values
|
| 277 |
+
all_fluids = {**COMMON_LIQUIDS, **COMMON_GASES}
|
| 278 |
+
fluid_name, (density, viscosity) = random.choice(list(all_fluids.items()))
|
| 279 |
+
|
| 280 |
+
# Randomly choose a flow geometry
|
| 281 |
+
geometry = random.choice(['pipe', 'flat plate'])
|
| 282 |
+
|
| 283 |
+
if geometry == 'pipe':
|
| 284 |
+
# Pipe flow parameters
|
| 285 |
+
characteristic_length = round(random.uniform(0.01, 0.5), 3) # Diameter in m
|
| 286 |
+
velocity = round(random.uniform(0.1, 5.0), 2)
|
| 287 |
+
length_symbol = "D"
|
| 288 |
+
length_name = "diameter"
|
| 289 |
+
scenario_description = f"{fluid_name} is flowing through a smooth circular pipe with an internal {length_name} of {characteristic_length} m."
|
| 290 |
+
|
| 291 |
+
else: # flat plate
|
| 292 |
+
# Flat plate flow parameters
|
| 293 |
+
characteristic_length = round(random.uniform(0.1, 2.0), 2) # Length in m
|
| 294 |
+
velocity = round(random.uniform(1.0, 20.0), 1)
|
| 295 |
+
length_symbol = "L"
|
| 296 |
+
length_name = "length"
|
| 297 |
+
scenario_description = f"A flow of {fluid_name} moves over a smooth, thin flat plate with a {length_name} of {characteristic_length} m."
|
| 298 |
+
|
| 299 |
+
# 2. Perform the core calculation
|
| 300 |
+
reynolds_number = (density * velocity * characteristic_length) / viscosity
|
| 301 |
+
|
| 302 |
+
# Determine flow regime based on geometry
|
| 303 |
+
if geometry == 'pipe':
|
| 304 |
+
if reynolds_number < 2300:
|
| 305 |
+
regime = "laminar"
|
| 306 |
+
regime_explanation = f"Since Re = {reynolds_number:,.0f} is less than 2300, the flow is in the **laminar** regime."
|
| 307 |
+
elif reynolds_number < 4000:
|
| 308 |
+
regime = "transitional"
|
| 309 |
+
regime_explanation = f"Since Re = {reynolds_number:,.0f} is between 2300 and 4000, the flow is in the **transitional** regime."
|
| 310 |
+
else:
|
| 311 |
+
regime = "turbulent"
|
| 312 |
+
regime_explanation = f"Since Re = {reynolds_number:,.0f} is greater than 4000, the flow is in the **turbulent** regime."
|
| 313 |
+
else: # flat plate
|
| 314 |
+
if reynolds_number < 500000:
|
| 315 |
+
regime = "laminar"
|
| 316 |
+
regime_explanation = f"Since Re = {reynolds_number:,.0f} is less than the critical value of 500,000, the flow is considered **laminar**."
|
| 317 |
+
else:
|
| 318 |
+
regime = "turbulent"
|
| 319 |
+
regime_explanation = f"Since Re = {reynolds_number:,.0f} is greater than the critical value of 500,000, the flow is considered **turbulent**."
|
| 320 |
+
|
| 321 |
+
# 3. Generate the question and solution strings
|
| 322 |
+
question = (
|
| 323 |
+
f"{scenario_description} The average velocity of the flow is {velocity} m/s.\n\n"
|
| 324 |
+
f"The properties of {fluid_name} are:\n"
|
| 325 |
+
f"- Density (ρ) = {density} kg/m³\n"
|
| 326 |
+
f"- Dynamic Viscosity (μ) = {viscosity:.2e} Pa·s\n\n"
|
| 327 |
+
f"Based on this information:\n"
|
| 328 |
+
f"a) Calculate the Reynolds number (Re).\n"
|
| 329 |
+
f"b) Determine the flow regime (laminar, transitional, or turbulent)."
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
solution = (
|
| 333 |
+
f"**Given Information:**\n"
|
| 334 |
+
f"- Fluid: {fluid_name}\n"
|
| 335 |
+
f"- Density (ρ): {density} kg/m³\n"
|
| 336 |
+
f"- Dynamic Viscosity (μ): {viscosity:.2e} Pa·s\n"
|
| 337 |
+
f"- Average Velocity (v): {velocity} m/s\n"
|
| 338 |
+
f"- Characteristic Length ({length_symbol}): {characteristic_length} m ({length_name} of the {geometry})\n\n"
|
| 339 |
+
|
| 340 |
+
f"**Step 1:** State the formula for the Reynolds number.\n"
|
| 341 |
+
f"The Reynolds number is the ratio of inertial forces to viscous forces.\n"
|
| 342 |
+
f"Re = (ρ * v * {length_symbol}) / μ\n\n"
|
| 343 |
+
|
| 344 |
+
f"**Step 2:** Substitute the values and calculate Re.\n"
|
| 345 |
+
f"Re = ({density} * {velocity} * {characteristic_length}) / {viscosity:.2e}\n"
|
| 346 |
+
f"Re = {reynolds_number:,.0f}\n\n"
|
| 347 |
+
|
| 348 |
+
f"**Step 3:** Determine the flow regime.\n"
|
| 349 |
+
f"For flow in a **{geometry}**, we compare the calculated Re to the standard critical values.\n"
|
| 350 |
+
f"{regime_explanation}\n\n"
|
| 351 |
+
|
| 352 |
+
f"**Answer:**\n"
|
| 353 |
+
f"a) The Reynolds number is approximately **{reynolds_number:,.0f}**.\n"
|
| 354 |
+
f"b) The flow regime is **{regime}**."
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
return question, solution
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
# Template 5 (Advanced)
|
| 361 |
+
def template_power_law_fluid_shear():
|
| 362 |
+
"""
|
| 363 |
+
Shear Stress Calculation for a Power-Law Fluid
|
| 364 |
+
|
| 365 |
+
Scenario:
|
| 366 |
+
This template extends Newton's Law of Viscosity to non-Newtonian fluids
|
| 367 |
+
that follow the Ostwald-de Waele (Power Law) model. Unlike Newtonian fluids,
|
| 368 |
+
their viscosity is dependent on the shear rate. This model is crucial for
|
| 369 |
+
describing common substances like paint, ketchup, and suspensions.
|
| 370 |
+
|
| 371 |
+
The relevant equations are:
|
| 372 |
+
- Shear Stress: τ_yx = K * (dvx/dy)^n
|
| 373 |
+
- Apparent Viscosity: η = K * |dvx/dy|^(n-1)
|
| 374 |
+
|
| 375 |
+
Returns:
|
| 376 |
+
tuple: A tuple containing:
|
| 377 |
+
- str: A question asking for shear stress and apparent viscosity.
|
| 378 |
+
- str: A step-by-step solution.
|
| 379 |
+
"""
|
| 380 |
+
# 1. Parameterize the inputs with random values
|
| 381 |
+
fluid_name, (K, n) = random.choice(list(POWER_LAW_FLUIDS.items()))
|
| 382 |
+
|
| 383 |
+
# Velocity of the moving plate in m/s
|
| 384 |
+
V = round(random.uniform(0.1, 2.0), 2)
|
| 385 |
+
|
| 386 |
+
# Distance between plates in cm, converted to meters for calculation
|
| 387 |
+
Y_cm = round(random.uniform(0.5, 5.0), 2)
|
| 388 |
+
Y_m = Y_cm / 100
|
| 389 |
+
|
| 390 |
+
# 2. Perform the core calculation
|
| 391 |
+
velocity_gradient = abs(V / Y_m)
|
| 392 |
+
shear_stress = K * (velocity_gradient ** n)
|
| 393 |
+
apparent_viscosity = K * (velocity_gradient ** (n - 1))
|
| 394 |
+
|
| 395 |
+
# Determine the fluid behavior for the explanation
|
| 396 |
+
if n < 1:
|
| 397 |
+
behavior = f"shear-thinning (pseudoplastic), because its power-law index n ({n}) is less than 1."
|
| 398 |
+
elif n > 1:
|
| 399 |
+
behavior = f"shear-thickening (dilatant), because its power-law index n ({n}) is greater than 1."
|
| 400 |
+
else:
|
| 401 |
+
behavior = "Newtonian, because its power-law index n is exactly 1."
|
| 402 |
+
|
| 403 |
+
# 3. Generate the question and solution strings
|
| 404 |
+
question = (
|
| 405 |
+
f"A non-Newtonian fluid, {fluid_name.lower()}, is placed between two parallel plates "
|
| 406 |
+
f"separated by {Y_cm} cm. The top plate moves at a constant velocity of {V} m/s, "
|
| 407 |
+
f"creating a linear velocity profile in the fluid.\n\n"
|
| 408 |
+
f"The fluid follows the power-law model with the following parameters:\n"
|
| 409 |
+
f"- Consistency Index (K) = {K} Pa·s^n\n"
|
| 410 |
+
f"- Power-Law Index (n) = {n}\n\n"
|
| 411 |
+
f"Calculate:\n"
|
| 412 |
+
f"a) The shear stress (τ_yx) on the fluid.\n"
|
| 413 |
+
f"b) The apparent viscosity (η) at this shear rate."
|
| 414 |
+
)
|
| 415 |
+
|
| 416 |
+
solution = (
|
| 417 |
+
f"**Given Information:**\n"
|
| 418 |
+
f"- Fluid: {fluid_name}\n"
|
| 419 |
+
f"- Consistency Index (K): {K} Pa·s^n\n"
|
| 420 |
+
f"- Power-Law Index (n): {n}\n"
|
| 421 |
+
f"- Plate Velocity (V): {V} m/s\n"
|
| 422 |
+
f"- Plate Separation (Y): {Y_cm} cm = {Y_m} m\n\n"
|
| 423 |
+
|
| 424 |
+
f"**Step 1:** Characterize the Fluid Behavior.\n"
|
| 425 |
+
f"The fluid is {behavior} This means its apparent viscosity will change with the rate of shear.\n\n"
|
| 426 |
+
|
| 427 |
+
f"**Step 2:** Calculate the Velocity Gradient (Shear Rate).\n"
|
| 428 |
+
f"Assuming a linear velocity profile:\n"
|
| 429 |
+
f"Shear Rate (dvx/dy) = V / Y = {V} m/s / {Y_m} m = {velocity_gradient:.2f} 1/s\n\n"
|
| 430 |
+
|
| 431 |
+
f"**Step 3:** Calculate the Shear Stress (τ_yx).\n"
|
| 432 |
+
f"Using the power-law formula: τ_yx = K * (dvx/dy)^n\n"
|
| 433 |
+
f"τ_yx = {K} * ({velocity_gradient:.2f})^{n}\n"
|
| 434 |
+
f"τ_yx = {shear_stress:.3f} Pa\n\n"
|
| 435 |
+
|
| 436 |
+
f"**Step 4:** Calculate the Apparent Viscosity (η).\n"
|
| 437 |
+
f"Apparent viscosity is the effective viscosity at a specific shear rate.\n"
|
| 438 |
+
f"η = K * |dvx/dy|^(n-1)\n"
|
| 439 |
+
f"η = {K} * ({velocity_gradient:.2f})^({n - 1})\n"
|
| 440 |
+
f"η = {apparent_viscosity:.4f} Pa·s\n\n"
|
| 441 |
+
|
| 442 |
+
f"**Answer:**\n"
|
| 443 |
+
f"a) The shear stress on the fluid is **{shear_stress:.3f} Pa**.\n"
|
| 444 |
+
f"b) The apparent viscosity at this shear rate is **{apparent_viscosity:.4f} Pa·s**."
|
| 445 |
+
)
|
| 446 |
+
|
| 447 |
+
return question, solution
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
def main():
|
| 451 |
+
"""
|
| 452 |
+
Generate numerous instances of each viscosity and mechanisms of momentum transport template
|
| 453 |
+
with different random seeds and write the results to a JSONL file.
|
| 454 |
+
"""
|
| 455 |
+
import json
|
| 456 |
+
import os
|
| 457 |
+
|
| 458 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 459 |
+
output_file = "testset/chemical_engineering/transport_phenomena/viscosity_and_momentum_transport.jsonl"
|
| 460 |
+
|
| 461 |
+
# Create the directory if it doesn't exist
|
| 462 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 463 |
+
|
| 464 |
+
# List of template functions with their ID and level
|
| 465 |
+
templates = [
|
| 466 |
+
(template_newtons_law_shear_stress, "newtons_law_shear_stress", "Easy"),
|
| 467 |
+
(template_kinematic_viscosity, "kinematic_viscosity", "Easy"),
|
| 468 |
+
(template_gas_viscosity_kinetic_theory, "gas_viscosity_kinetic_theory", "Intermediate"),
|
| 469 |
+
(template_reynolds_number_flow_regime, "reynolds_number_flow_regime", "Intermediate"),
|
| 470 |
+
(template_power_law_fluid_shear, "power_law_fluid_shear", "Advanced"),
|
| 471 |
+
]
|
| 472 |
+
|
| 473 |
+
# List to store all generated problems
|
| 474 |
+
all_problems = []
|
| 475 |
+
|
| 476 |
+
# Generate problems for each template
|
| 477 |
+
for template_func, id_name, level in templates:
|
| 478 |
+
for _ in range(50):
|
| 479 |
+
# Generate a unique seed for each problem
|
| 480 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 481 |
+
random.seed(seed)
|
| 482 |
+
|
| 483 |
+
# Generate the problem and solution
|
| 484 |
+
question, solution = template_func()
|
| 485 |
+
|
| 486 |
+
# Create a JSON entry
|
| 487 |
+
problem_entry = {
|
| 488 |
+
"seed": seed,
|
| 489 |
+
"branch": "chemical_engineering",
|
| 490 |
+
"domain": "transport_phenomena",
|
| 491 |
+
"area": "viscosity_and_momentum_transport",
|
| 492 |
+
"id": id_name,
|
| 493 |
+
"level": level,
|
| 494 |
+
"question": question,
|
| 495 |
+
"solution": solution
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
# Add to the list of problems
|
| 499 |
+
all_problems.append(problem_entry)
|
| 500 |
+
|
| 501 |
+
# Shuffle the problems to mix templates and levels
|
| 502 |
+
random.shuffle(all_problems)
|
| 503 |
+
|
| 504 |
+
# Write all problems to a .jsonl file
|
| 505 |
+
with open(output_file, "w") as file:
|
| 506 |
+
for problem in all_problems:
|
| 507 |
+
file.write(json.dumps(problem))
|
| 508 |
+
file.write("\n")
|
| 509 |
+
|
| 510 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 511 |
+
|
| 512 |
+
|
| 513 |
+
if __name__ == "__main__":
|
| 514 |
+
main()
|
data/templates/branches/electrical_engineering/__pycache__/constants.cpython-311.pyc
ADDED
|
Binary file (1.91 kB). View file
|
|
|
data/templates/branches/electrical_engineering/constants.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
|
| 3 |
+
# Speed of light in vacuum (m/s)
|
| 4 |
+
C0 = 299792458
|
| 5 |
+
|
| 6 |
+
# Dictionary of common media and their approximate phase velocities for EM waves
|
| 7 |
+
MEDIA_VELOCITIES = {
|
| 8 |
+
# Gases (at 0°C and 1 atm, for visible light ~589 nm)
|
| 9 |
+
"Vacuum": C0,
|
| 10 |
+
"Air (at sea level)": C0 / 1.000293,
|
| 11 |
+
"Helium": C0 / 1.000036,
|
| 12 |
+
"Carbon Dioxide": C0 / 1.00045,
|
| 13 |
+
|
| 14 |
+
# Liquids (for visible light ~589 nm)
|
| 15 |
+
"Water (distilled, 20°C)": C0 / 1.333,
|
| 16 |
+
"Ethanol": C0 / 1.36,
|
| 17 |
+
"Glycerine": C0 / 1.473,
|
| 18 |
+
"Benzene": C0 / 1.501,
|
| 19 |
+
"Carbon Disulfide": C0 / 1.628, # notable for high dispersion
|
| 20 |
+
|
| 21 |
+
# Solids (for visible light ~589 nm)
|
| 22 |
+
"Ice": C0 / 1.31,
|
| 23 |
+
"Teflon (PTFE)": C0 / 1.35,
|
| 24 |
+
"Fused Silica (Glass)": C0 / 1.458,
|
| 25 |
+
"Crown Glass (typical)": C0 / 1.52,
|
| 26 |
+
"Polyethylene": C0 / 1.54,
|
| 27 |
+
"Polystyrene": C0 / 1.59,
|
| 28 |
+
"Flint Glass (dense)": C0 / 1.65,
|
| 29 |
+
"Sapphire": C0 / 1.77,
|
| 30 |
+
"Glass (amorphous semiconductor)": C0 / 1.8,
|
| 31 |
+
"Diamond": C0 / 2.42,
|
| 32 |
+
"Gallium Phosphide (GaP)": C0 / 3.5,
|
| 33 |
+
|
| 34 |
+
# Special Cases (Important for RF/Microwave Engineering)
|
| 35 |
+
"Human Body Tissue (muscle, ~3 GHz)": C0 / 7.14, # Relative permittivity ε_r ~51, n=√ε_r
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
# Permittivity of free space in Farads per meter (F/m)
|
| 39 |
+
EPSILON_0 = 8.854e-12
|
| 40 |
+
|
| 41 |
+
# Value ranges for random parameter generation to ensure diverse problems.
|
| 42 |
+
# Frequencies are kept as integers for clarity in the problem statement.
|
| 43 |
+
FREQUENCY_RANGE_HZ = (50, 2000)
|
| 44 |
+
AMPLITUDE_RANGE = (1.0, 50.0)
|
| 45 |
+
PHASE_RANGE_DEG = (-180, 180)
|
| 46 |
+
PHASE_RANGE_RAD = (-math.pi, math.pi)
|
| 47 |
+
|
| 48 |
+
# The continuous frequency Omega will be a multiple of pi. This range defines the multiplier.
|
| 49 |
+
OMEGA_MULTIPLIER_RANGE = (100, 1000)
|
| 50 |
+
|
| 51 |
+
SAMPLING_FREQ_RANGE_HZ = (1000, 8000)
|
| 52 |
+
|
| 53 |
+
F0_RANGE_HZ = (500, 3000)
|
| 54 |
+
|
| 55 |
+
# The gain of the discrete-time system
|
| 56 |
+
GAIN_K_RANGE = (0.5, 5.0)
|
| 57 |
+
|
| 58 |
+
# The delay (in samples) of the discrete-time system
|
| 59 |
+
DELAY_N0_RANGE = (1, 10)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# The integer factor by which the signal is downsampled.
|
| 63 |
+
DECIMATION_FACTOR_M_RANGE = (2, 5)
|
| 64 |
+
|
| 65 |
+
# Define the pool for denominators of the omega_0 fraction.
|
| 66 |
+
# Using larger numbers allows for more granularity in creating frequencies.
|
| 67 |
+
OMEGA_DENOMINATOR_RANGE = (8, 20)
|
data/templates/branches/electrical_engineering/digital_communications/__pycache__/deterministic_and_random_signal_analysis.cpython-311.pyc
ADDED
|
Binary file (29.2 kB). View file
|
|
|
data/templates/branches/electrical_engineering/digital_communications/__pycache__/digital_modulation_schemes.cpython-311.pyc
ADDED
|
Binary file (28.2 kB). View file
|
|
|
data/templates/branches/electrical_engineering/digital_communications/deterministic_and_random_signal_analysis.py
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_signal_energy_power():
|
| 7 |
+
"""
|
| 8 |
+
Signal Energy and Power Calculation
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template tests the ability to classify a signal as either an energy
|
| 12 |
+
signal (finite energy) or a power signal (finite average power) and to
|
| 13 |
+
calculate the corresponding value. It covers time-limited, time-unlimited
|
| 14 |
+
decaying, and periodic signals.
|
| 15 |
+
|
| 16 |
+
Core Equations:
|
| 17 |
+
Signal Energy: Eg = integral from -inf to inf of |g(t)|^2 dt
|
| 18 |
+
Signal Power: Pg = lim(T->inf) (1/T) * integral from -T/2 to T/2 of |g(t)|^2 dt
|
| 19 |
+
|
| 20 |
+
Returns:
|
| 21 |
+
tuple: A tuple containing:
|
| 22 |
+
- str: A question asking to classify and calculate the energy/power of a signal.
|
| 23 |
+
- str: A step-by-step solution.
|
| 24 |
+
"""
|
| 25 |
+
# 1. Parameterize the inputs with random values
|
| 26 |
+
signal_type = random.choice(['rect_pulse', 'exp_decay', 'periodic'])
|
| 27 |
+
amplitude = random.randint(2, 20)
|
| 28 |
+
precision = 2
|
| 29 |
+
|
| 30 |
+
# Common text for all questions to ensure unit clarity
|
| 31 |
+
unit_context = "Assume the signal g(t) represents a voltage in Volts (V) across a 1-ohm resistor."
|
| 32 |
+
|
| 33 |
+
if signal_type == 'rect_pulse':
|
| 34 |
+
# --- Energy Signal: Rectangular Pulse ---
|
| 35 |
+
duration = random.randint(2, 10)
|
| 36 |
+
start_time = 0
|
| 37 |
+
end_time = start_time + duration
|
| 38 |
+
signal_expr = f"g(t) = {amplitude} for {start_time} <= t <= {end_time}, and 0 otherwise"
|
| 39 |
+
energy = (amplitude ** 2) * duration
|
| 40 |
+
|
| 41 |
+
question = (
|
| 42 |
+
f"Consider the signal defined as:\n"
|
| 43 |
+
f"{signal_expr}\n\n"
|
| 44 |
+
f"{unit_context}\n\n"
|
| 45 |
+
f"Is this an energy signal or a power signal? Calculate its total energy or "
|
| 46 |
+
f"average power accordingly."
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
solution = (
|
| 50 |
+
f"**Given:**\n"
|
| 51 |
+
f"The signal is {signal_expr}.\n\n"
|
| 52 |
+
|
| 53 |
+
f"**Step 1:** Classify the signal.\n"
|
| 54 |
+
f"The signal is non-zero only for a finite duration ({duration} seconds). "
|
| 55 |
+
f"Time-limited signals have finite energy and zero average power. "
|
| 56 |
+
f"Therefore, this is an **energy signal**.\n\n"
|
| 57 |
+
|
| 58 |
+
f"**Step 2:** State the formula for signal energy.\n"
|
| 59 |
+
f"The energy (Eg) of a signal g(t) is:\n"
|
| 60 |
+
f"Eg = integral from -inf to inf of |g(t)|^2 dt\n\n"
|
| 61 |
+
|
| 62 |
+
f"**Step 3:** Apply the formula to the given signal.\n"
|
| 63 |
+
f"Since the signal is non-zero only between t = {start_time} and t = {end_time}, the integral becomes:\n"
|
| 64 |
+
f"Eg = integral from {start_time} to {end_time} of ({amplitude})^2 dt = integral from {start_time} to {end_time} of {amplitude**2} dt\n\n"
|
| 65 |
+
|
| 66 |
+
f"**Step 4:** Calculate the integral.\n"
|
| 67 |
+
f"Eg = {amplitude**2} * [t] (from t={start_time} to {end_time})\n"
|
| 68 |
+
f"Eg = {amplitude**2} * ({end_time} - {start_time}) = {amplitude**2} * {duration} = {energy}\n\n"
|
| 69 |
+
|
| 70 |
+
f"**Answer:**\n"
|
| 71 |
+
f"The signal is an **energy signal** with a total energy of **{round(energy, precision)} Joules (V^2 * s)**."
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
elif signal_type == 'exp_decay':
|
| 75 |
+
# --- Energy Signal: Exponential Decay ---
|
| 76 |
+
alpha = random.randint(2, 10)
|
| 77 |
+
signal_expr = f"g(t) = {amplitude} * exp(-{alpha}*t) * u(t)"
|
| 78 |
+
energy = (amplitude ** 2) / (2 * alpha)
|
| 79 |
+
|
| 80 |
+
question = (
|
| 81 |
+
f"Consider the signal defined as:\n"
|
| 82 |
+
f"{signal_expr}\n"
|
| 83 |
+
f"where u(t) is the unit step function.\n\n"
|
| 84 |
+
f"{unit_context}\n\n"
|
| 85 |
+
f"Is this an energy signal or a power signal? Calculate its total energy or "
|
| 86 |
+
f"average power accordingly."
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
solution = (
|
| 90 |
+
f"**Given:**\n"
|
| 91 |
+
f"The signal is {signal_expr}.\n\n"
|
| 92 |
+
|
| 93 |
+
f"**Step 1:** Classify the signal.\n"
|
| 94 |
+
f"The signal is not time-limited, but it decays to zero as t approaches infinity. "
|
| 95 |
+
f"This means its total energy is finite. Therefore, this is an **energy signal**.\n\n"
|
| 96 |
+
|
| 97 |
+
f"**Step 2:** State the formula for signal energy.\n"
|
| 98 |
+
f"Eg = integral from -inf to inf of |g(t)|^2 dt\n\n"
|
| 99 |
+
|
| 100 |
+
f"**Step 3:** Apply the formula to the given signal.\n"
|
| 101 |
+
f"Due to the unit step function u(t), the integral's limits become 0 to infinity:\n"
|
| 102 |
+
f"Eg = integral from 0 to inf of ({amplitude} * exp(-{alpha}*t))^2 dt\n"
|
| 103 |
+
f"Eg = {amplitude**2} * integral from 0 to inf of exp(-{2*alpha}*t) dt\n\n"
|
| 104 |
+
|
| 105 |
+
f"**Step 4:** Calculate the integral.\n"
|
| 106 |
+
f"Eg = {amplitude**2} * [-1/{2*alpha} * exp(-{2*alpha}*t)] (from t=0 to inf)\n"
|
| 107 |
+
f"Eg = {amplitude**2} * ( (0) - (-1/{2*alpha} * exp(0)) ) = {amplitude**2} * (1/{2*alpha})\n"
|
| 108 |
+
f"Eg = {amplitude**2} / {2*alpha} = {energy}\n\n"
|
| 109 |
+
|
| 110 |
+
f"**Answer:**\n"
|
| 111 |
+
f"The signal is an **energy signal** with a total energy of **{round(energy, precision)} Joules (V^2 * s)**."
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
else: # signal_type == 'periodic'
|
| 115 |
+
# --- Power Signal: Periodic Sinusoid ---
|
| 116 |
+
frequency = random.randint(10, 100)
|
| 117 |
+
signal_expr = f"g(t) = {amplitude} * cos(2 * pi * {frequency} * t)"
|
| 118 |
+
power = (amplitude ** 2) / 2
|
| 119 |
+
|
| 120 |
+
question = (
|
| 121 |
+
f"Consider the signal defined as:\n"
|
| 122 |
+
f"{signal_expr}\n\n"
|
| 123 |
+
f"{unit_context}\n\n"
|
| 124 |
+
f"Is this an energy signal or a power signal? Calculate its total energy or "
|
| 125 |
+
f"average power accordingly."
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
solution = (
|
| 129 |
+
f"**Given:**\n"
|
| 130 |
+
f"The signal is {signal_expr}.\n\n"
|
| 131 |
+
|
| 132 |
+
f"**Step 1:** Classify the signal.\n"
|
| 133 |
+
f"The signal is periodic and continues for all time without decaying. Its total energy would be infinite. "
|
| 134 |
+
f"However, its average power over time is finite and non-zero. "
|
| 135 |
+
f"Therefore, this is a **power signal**.\n\n"
|
| 136 |
+
|
| 137 |
+
f"**Step 2:** State the formula for signal power.\n"
|
| 138 |
+
f"For a periodic signal with period T0, the average power (Pg) is calculated over one period:\n"
|
| 139 |
+
f"Pg = (1/T0) * integral over T0 of |g(t)|^2 dt\n\n"
|
| 140 |
+
|
| 141 |
+
f"**Step 3:** Apply the formula to the given signal.\n"
|
| 142 |
+
f"For any sinusoidal signal of the form A*cos(w*t + phi), the average power is a standard result:\n"
|
| 143 |
+
f"Pg = A^2 / 2\n"
|
| 144 |
+
f"This result is derived from integrating (A * cos(...))^2 over one full period.\n\n"
|
| 145 |
+
|
| 146 |
+
f"**Step 4:** Calculate the power.\n"
|
| 147 |
+
f"In this case, the amplitude A is {amplitude}.\n"
|
| 148 |
+
f"Pg = ({amplitude})^2 / 2\n"
|
| 149 |
+
f"Pg = {amplitude**2} / 2 = {power}\n\n"
|
| 150 |
+
|
| 151 |
+
f"**Answer:**\n"
|
| 152 |
+
f"The signal is a **power signal** with an average power of **{round(power, precision)} Watts (V^2)**."
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
return question, solution
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
# Template 2 (Easy)
|
| 159 |
+
def template_mean_variance():
|
| 160 |
+
"""
|
| 161 |
+
Mean and Variance of a Random Variable
|
| 162 |
+
|
| 163 |
+
Scenario:
|
| 164 |
+
This template assesses the understanding of basic statistical properties of a
|
| 165 |
+
discrete random variable, specifically its central tendency (mean) and
|
| 166 |
+
dispersion (variance).
|
| 167 |
+
|
| 168 |
+
Core Equations:
|
| 169 |
+
Mean (Expected Value): mu_X = E[X] = sum(x_i * P(x_i))
|
| 170 |
+
Variance: sigma_X^2 = E[(X - mu_X)^2] = sum((x_i - mu_X)^2 * P(x_i))
|
| 171 |
+
|
| 172 |
+
Returns:
|
| 173 |
+
tuple: A tuple containing:
|
| 174 |
+
- str: A question asking for the mean and variance of a discrete random variable.
|
| 175 |
+
- str: A step-by-step solution.
|
| 176 |
+
"""
|
| 177 |
+
# 1. Parameterize the inputs with random values
|
| 178 |
+
num_values = random.randint(3, 5)
|
| 179 |
+
precision = 3
|
| 180 |
+
|
| 181 |
+
# Generate a set of unique, sorted integer values for the random variable
|
| 182 |
+
values = sorted(random.sample(range(-10, 21), num_values))
|
| 183 |
+
|
| 184 |
+
# Generate clean, rational probabilities that sum to 1
|
| 185 |
+
# Create random integer parts, sum them, and use that sum as the denominator
|
| 186 |
+
parts = [random.randint(1, 10) for _ in range(num_values)]
|
| 187 |
+
total_sum = sum(parts)
|
| 188 |
+
probabilities = [p / total_sum for p in parts]
|
| 189 |
+
|
| 190 |
+
# 2. Perform the core calculations
|
| 191 |
+
# Calculate the mean (expected value)
|
| 192 |
+
mean = sum(v * p for v, p in zip(values, probabilities))
|
| 193 |
+
|
| 194 |
+
# Calculate the variance
|
| 195 |
+
variance = sum(((v - mean) ** 2) * p for v, p in zip(values, probabilities))
|
| 196 |
+
|
| 197 |
+
# 3. Generate the question and solution strings
|
| 198 |
+
|
| 199 |
+
# Format values and probabilities for display
|
| 200 |
+
values_str = "{" + ", ".join(map(str, values)) + "}"
|
| 201 |
+
probs_str = "{" + ", ".join([f"{p:.{precision}f}" for p in probabilities]) + "}"
|
| 202 |
+
|
| 203 |
+
question = (
|
| 204 |
+
f"A discrete random variable X can take the values {values_str} with "
|
| 205 |
+
f"corresponding probabilities {probs_str}, respectively.\n\n"
|
| 206 |
+
f"Calculate the mean (mu_X) and variance (sigma_X^2) of X."
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
# --- Build the solution string step-by-step ---
|
| 210 |
+
|
| 211 |
+
# Mean calculation steps
|
| 212 |
+
mean_calc_lhs = "mu_X = E[X]"
|
| 213 |
+
mean_calc_rhs = " + ".join([f"({v})({p:.{precision}f})" for v, p in zip(values, probabilities)])
|
| 214 |
+
mean_interm_vals = " + ".join([f"{v * p:.{precision}f}" for v, p in zip(values, probabilities)])
|
| 215 |
+
|
| 216 |
+
# Variance calculation steps
|
| 217 |
+
var_calc_lhs = "sigma_X^2 = E[(X - mu_X)^2]"
|
| 218 |
+
var_calc_rhs = " + ".join(
|
| 219 |
+
[f"({v} - {mean:.{precision}f})^2({p:.{precision}f})" for v, p in zip(values, probabilities)]
|
| 220 |
+
)
|
| 221 |
+
var_interm_vals = " + ".join(
|
| 222 |
+
[f"({(v - mean):.{precision}f})^2({p:.{precision}f})" for v, p in zip(values, probabilities)]
|
| 223 |
+
)
|
| 224 |
+
var_final_vals = " + ".join(
|
| 225 |
+
[f"{((v - mean)**2 * p):.{precision}f}" for v, p in zip(values, probabilities)]
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
solution = (
|
| 229 |
+
f"**Given:**\n"
|
| 230 |
+
f"Values: X = {values_str}\n"
|
| 231 |
+
f"Probabilities: P(X) = {probs_str}\n\n"
|
| 232 |
+
|
| 233 |
+
f"**Step 1:** Calculate the Mean (mu_X).\n"
|
| 234 |
+
f"The formula for the mean (expected value) is:\n"
|
| 235 |
+
f"mu_X = sum(x_i * P(x_i))\n\n"
|
| 236 |
+
f"Plugging in the given values:\n"
|
| 237 |
+
f"{mean_calc_lhs} = {mean_calc_rhs}\n"
|
| 238 |
+
f"{mean_calc_lhs} = {mean_interm_vals}\n"
|
| 239 |
+
f"{mean_calc_lhs} = {mean:.{precision}f}\n\n"
|
| 240 |
+
|
| 241 |
+
f"**Step 2:** Calculate the Variance (sigma_X^2).\n"
|
| 242 |
+
f"The formula for variance is:\n"
|
| 243 |
+
f"sigma_X^2 = sum((x_i - mu_X)^2 * P(x_i))\n\n"
|
| 244 |
+
f"Using the calculated mean (mu_X = {mean:.{precision}f}):\n"
|
| 245 |
+
f"{var_calc_lhs} = {var_calc_rhs}\n"
|
| 246 |
+
f"{var_calc_lhs} = {var_interm_vals}\n"
|
| 247 |
+
f"{var_calc_lhs} = {var_final_vals}\n"
|
| 248 |
+
f"{var_calc_lhs} = {variance:.{precision}f}\n\n"
|
| 249 |
+
|
| 250 |
+
f"**Answer:**\n"
|
| 251 |
+
f"The mean of the random variable X is **{mean:.{precision}f}**.\n"
|
| 252 |
+
f"The variance of the random variable X is **{variance:.{precision}f}**."
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
return question, solution
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
# Template 3 (Intermediate)
|
| 259 |
+
def template_autocorrelation_rect_pulse():
|
| 260 |
+
"""
|
| 261 |
+
Autocorrelation of a Deterministic Signal
|
| 262 |
+
|
| 263 |
+
Scenario:
|
| 264 |
+
This template tests the ability to calculate the autocorrelation function of
|
| 265 |
+
a simple deterministic signal, a rectangular pulse. This measures the
|
| 266 |
+
similarity of the signal with a time-shifted version of itself and
|
| 267 |
+
introduces the concept of deriving a new function shape (a triangle) from
|
| 268 |
+
this operation.
|
| 269 |
+
|
| 270 |
+
Core Equation:
|
| 271 |
+
Autocorrelation: R_g(tau) = integral from -inf to inf of g(t) * g(t - tau) dt
|
| 272 |
+
|
| 273 |
+
Returns:
|
| 274 |
+
tuple: A tuple containing:
|
| 275 |
+
- str: A question asking for the autocorrelation of a rectangular pulse.
|
| 276 |
+
- str: A step-by-step solution.
|
| 277 |
+
"""
|
| 278 |
+
# 1. Parameterize the inputs with random values
|
| 279 |
+
amplitude = random.randint(2, 10)
|
| 280 |
+
|
| 281 |
+
# Use a half-duration to ensure the total duration is an even number
|
| 282 |
+
# and the pulse limits are clean integers.
|
| 283 |
+
half_duration = random.randint(1, 5)
|
| 284 |
+
duration = 2 * half_duration
|
| 285 |
+
|
| 286 |
+
# 2. Perform the core calculation
|
| 287 |
+
# The autocorrelation of a rectangular pulse is a triangular function.
|
| 288 |
+
# Peak value R(0) = A^2 * T
|
| 289 |
+
peak_value = (amplitude ** 2) * duration
|
| 290 |
+
|
| 291 |
+
# The resulting function is R(tau) = A^2 * (T - |tau|) for |tau| <= T
|
| 292 |
+
result_function_str = f"{amplitude**2}*({duration} - |tau|)"
|
| 293 |
+
|
| 294 |
+
# 3. Generate the question and solution strings
|
| 295 |
+
signal_expr = f"g(t) = {amplitude} for -{half_duration} <= t <= {half_duration}, and 0 otherwise"
|
| 296 |
+
|
| 297 |
+
question = (
|
| 298 |
+
f"Find the autocorrelation function R_g(tau) for the rectangular pulse signal defined as:\n"
|
| 299 |
+
f" {signal_expr}"
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
solution = (
|
| 303 |
+
f"**Given:**\n"
|
| 304 |
+
f"The signal is a rectangular pulse: {signal_expr}.\n"
|
| 305 |
+
f"It has an amplitude A = {amplitude} and a total duration T = {duration}.\n\n"
|
| 306 |
+
|
| 307 |
+
f"**Step 1:** Understand the Autocorrelation Integral.\n"
|
| 308 |
+
f"The autocorrelation function is defined as R_g(tau) = integral of g(t) * g(t - tau) dt.\n"
|
| 309 |
+
f"Conceptually, this measures the overlapping area between the signal g(t) and a version of itself shifted by 'tau'.\n\n"
|
| 310 |
+
|
| 311 |
+
f"**Step 2:** Analyze the Overlap of the Pulses.\n"
|
| 312 |
+
f"The original pulse g(t) exists from t = -{half_duration} to t = {half_duration}.\n"
|
| 313 |
+
f"The shifted pulse g(t - tau) exists from t = tau - {half_duration} to t = tau + {half_duration}.\n"
|
| 314 |
+
f"The product g(t) * g(t - tau) is non-zero only where these two pulses overlap.\n"
|
| 315 |
+
f"In the overlapping region, the value of the product is ({amplitude}) * ({amplitude}) = {amplitude**2}.\n"
|
| 316 |
+
f"The pulses only overlap when the shift 'tau' is between -{duration} and {duration}. Outside this range, the autocorrelation is zero.\n\n"
|
| 317 |
+
|
| 318 |
+
f"**Step 3:** Determine the Area of the Overlap.\n"
|
| 319 |
+
f"The integral is simply the area of the rectangular overlapping region.\n"
|
| 320 |
+
f"Area = (Height of Overlap) * (Width of Overlap)\n\n"
|
| 321 |
+
f"The height is constant at {amplitude**2}.\n"
|
| 322 |
+
f"The width (duration) of the overlap depends on the shift 'tau'. When tau = 0, the overlap is the entire pulse duration, {duration}. As |tau| increases, the overlap width decreases linearly. The width is given by the expression: ({duration} - |tau|).\n\n"
|
| 323 |
+
|
| 324 |
+
f"**Step 4:** Formulate the Autocorrelation Function.\n"
|
| 325 |
+
f"Combining the height and width, the area of overlap is:\n"
|
| 326 |
+
f"R_g(tau) = {amplitude**2} * ({duration} - |tau|)\n\n"
|
| 327 |
+
f"This equation is valid for the range where overlap occurs, which is |tau| <= {duration}. For |tau| > {duration}, R_g(tau) = 0.\n\n"
|
| 328 |
+
|
| 329 |
+
f"**Answer:**\n"
|
| 330 |
+
f"The autocorrelation function is a **triangular function** described by:\n"
|
| 331 |
+
f"**R_g(tau) = {result_function_str}**, for **|tau| <= {duration}**, and **0** otherwise.\n"
|
| 332 |
+
f"This triangle has a peak value of **{peak_value}** at tau = 0."
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
return question, solution
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
# Template 4 (Intermediate)
|
| 339 |
+
def template_ft_esd_rect_pulse():
|
| 340 |
+
"""
|
| 341 |
+
Fourier Transform and Energy Spectral Density (ESD)
|
| 342 |
+
|
| 343 |
+
Scenario:
|
| 344 |
+
This template requires finding the Fourier Transform of a simple energy
|
| 345 |
+
signal (a rectangular pulse) and then calculating its Energy Spectral
|
| 346 |
+
Density (ESD). It demonstrates the fundamental relationship between a
|
| 347 |
+
signal's time-domain representation and its frequency-domain energy distribution.
|
| 348 |
+
|
| 349 |
+
Core Equations:
|
| 350 |
+
Fourier Transform: G(f) = integral from -inf to inf of g(t)*exp(-j*2*pi*f*t) dt
|
| 351 |
+
Energy Spectral Density (ESD): Psi_g(f) = |G(f)|^2
|
| 352 |
+
|
| 353 |
+
Returns:
|
| 354 |
+
tuple: A tuple containing:
|
| 355 |
+
- str: A question asking for the FT and ESD of a rectangular pulse.
|
| 356 |
+
- str: A step-by-step solution.
|
| 357 |
+
"""
|
| 358 |
+
# 1. Parameterize the inputs with random values
|
| 359 |
+
amplitude = random.randint(5, 20)
|
| 360 |
+
duration = random.choice([0.5, 1.0, 1.5, 2.0, 4.0])
|
| 361 |
+
half_duration = duration / 2
|
| 362 |
+
|
| 363 |
+
# 2. Perform the core calculation and format results
|
| 364 |
+
ft_amplitude = amplitude * duration
|
| 365 |
+
esd_amplitude = ft_amplitude ** 2
|
| 366 |
+
|
| 367 |
+
# Handle sinc argument formatting for cleaner output
|
| 368 |
+
if duration == 1.0:
|
| 369 |
+
sinc_arg_str = "f"
|
| 370 |
+
else:
|
| 371 |
+
sinc_arg_str = f"{duration}*f"
|
| 372 |
+
|
| 373 |
+
ft_result_str = f"{ft_amplitude} * sinc({sinc_arg_str})"
|
| 374 |
+
esd_result_str = f"{esd_amplitude} * sinc^2({sinc_arg_str})"
|
| 375 |
+
|
| 376 |
+
# 3. Generate the question and solution strings
|
| 377 |
+
signal_expr = f"g(t) with amplitude {amplitude} extending from t = -{half_duration} s to t = {half_duration} s"
|
| 378 |
+
|
| 379 |
+
question = (
|
| 380 |
+
f"Given a rectangular pulse {signal_expr}, find its Fourier Transform G(f) and "
|
| 381 |
+
f"its Energy Spectral Density Psi_g(f).\n\n"
|
| 382 |
+
f"Note: Use the definition sinc(x) = sin(pi*x) / (pi*x)."
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
+
solution = (
|
| 386 |
+
f"**Given:**\n"
|
| 387 |
+
f"A rectangular pulse g(t) with:\n"
|
| 388 |
+
f"Amplitude (A) = {amplitude}\n"
|
| 389 |
+
f"Total Duration (T) = {duration} s (from -{half_duration} to {half_duration})\n\n"
|
| 390 |
+
|
| 391 |
+
f"**Step 1:** Identify the Fourier Transform Pair for a Rectangular Pulse.\n"
|
| 392 |
+
f"A rectangular pulse centered at the origin, g(t) = A for |t| <= T/2, has a well-known Fourier Transform which is a sinc function:\n"
|
| 393 |
+
f"G(f) = A * T * sinc(f * T)\n"
|
| 394 |
+
f"where sinc(x) is defined as sin(pi*x) / (pi*x).\n\n"
|
| 395 |
+
|
| 396 |
+
f"**Step 2:** Calculate the Fourier Transform G(f).\n"
|
| 397 |
+
f"We plug in our given values A = {amplitude} and T = {duration} into the formula:\n"
|
| 398 |
+
f"G(f) = ({amplitude}) * ({duration}) * sinc(f * {duration})\n"
|
| 399 |
+
f"G(f) = {ft_result_str}\n\n"
|
| 400 |
+
|
| 401 |
+
f"**Step 3:** State the Formula for Energy Spectral Density (ESD).\n"
|
| 402 |
+
f"The ESD, denoted Psi_g(f), describes how the energy of the signal is distributed over frequency. It is calculated as the squared magnitude of the Fourier Transform:\n"
|
| 403 |
+
f"Psi_g(f) = |G(f)|^2\n\n"
|
| 404 |
+
|
| 405 |
+
f"**Step 4:** Calculate the Energy Spectral Density Psi_g(f).\n"
|
| 406 |
+
f"We take the magnitude squared of the G(f) we found in Step 2. Since our G(f) is real-valued, this is equivalent to just squaring the function:\n"
|
| 407 |
+
f"Psi_g(f) = |{ft_result_str}|^2\n"
|
| 408 |
+
f"Psi_g(f) = ({amplitude} * {duration})^2 * sinc^2({sinc_arg_str})\n"
|
| 409 |
+
f"Psi_g(f) = {esd_result_str}\n\n"
|
| 410 |
+
|
| 411 |
+
f"**Answer:**\n"
|
| 412 |
+
f"The Fourier Transform is **G(f) = {ft_result_str}**.\n"
|
| 413 |
+
f"The Energy Spectral Density is **Psi_g(f) = {esd_result_str}**."
|
| 414 |
+
)
|
| 415 |
+
|
| 416 |
+
return question, solution
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
# Template 5 (Advanced)
|
| 420 |
+
def template_lowpass_equivalent_bandpass():
|
| 421 |
+
"""
|
| 422 |
+
Lowpass Equivalent of a Bandpass Signal
|
| 423 |
+
|
| 424 |
+
Scenario:
|
| 425 |
+
A fundamental concept in communications is representing a high-frequency bandpass
|
| 426 |
+
signal using a lower-frequency complex equivalent. This template tests the
|
| 427 |
+
ability to derive this lowpass equivalent signal from a given bandpass
|
| 428 |
+
signal by expanding it into its canonical in-phase and quadrature components.
|
| 429 |
+
|
| 430 |
+
Core Equations:
|
| 431 |
+
Bandpass Signal: g(t) = g_I(t)*cos(2*pi*fc*t) - g_Q(t)*sin(2*pi*fc*t)
|
| 432 |
+
Lowpass Equivalent Signal: g_tilde(t) = g_I(t) + j*g_Q(t)
|
| 433 |
+
Trigonometric Identity: cos(a + b) = cos(a)*cos(b) - sin(a)*sin(b)
|
| 434 |
+
|
| 435 |
+
Returns:
|
| 436 |
+
tuple: A tuple containing:
|
| 437 |
+
- str: A question asking for the components of a bandpass signal.
|
| 438 |
+
- str: A step-by-step solution showing the derivation.
|
| 439 |
+
"""
|
| 440 |
+
# 1. Parameterize the inputs with random values
|
| 441 |
+
amplitude = random.randint(10, 100)
|
| 442 |
+
fc_exp = random.randint(6, 8) # For MHz to hundreds of MHz
|
| 443 |
+
fc_mult = random.randint(1, 9)
|
| 444 |
+
phase_deg = random.choice([30, 45, 60, 90, 120, 135, 150])
|
| 445 |
+
precision = 2
|
| 446 |
+
|
| 447 |
+
# Create a readable string for the carrier frequency
|
| 448 |
+
fc_str = f"{fc_mult} x 10^{fc_exp}"
|
| 449 |
+
|
| 450 |
+
# 2. Perform the core calculations
|
| 451 |
+
phase_rad = math.radians(phase_deg)
|
| 452 |
+
cos_phi = math.cos(phase_rad)
|
| 453 |
+
sin_phi = math.sin(phase_rad)
|
| 454 |
+
|
| 455 |
+
# From the identity, g(t) = A*cos(phi)*cos(w_c*t) - A*sin(phi)*sin(w_c*t)
|
| 456 |
+
# We can identify the in-phase and quadrature components
|
| 457 |
+
g_I = amplitude * cos_phi
|
| 458 |
+
g_Q = amplitude * sin_phi
|
| 459 |
+
|
| 460 |
+
# 3. Generate the question and solution strings
|
| 461 |
+
signal_expr = f"g(t) = {amplitude} * cos(2*pi*({fc_str})*t + {phase_deg} deg)"
|
| 462 |
+
|
| 463 |
+
question = (
|
| 464 |
+
f"A bandpass signal is described by the following equation:\n"
|
| 465 |
+
f"{signal_expr}\n\n"
|
| 466 |
+
f"Express this signal in its canonical form to determine its in-phase component (g_I(t)), "
|
| 467 |
+
f"quadrature component (g_Q(t)), and its complex lowpass equivalent signal (g_tilde(t))."
|
| 468 |
+
)
|
| 469 |
+
|
| 470 |
+
solution = (
|
| 471 |
+
f"**Given:**\n"
|
| 472 |
+
f"The bandpass signal is {signal_expr}.\n\n"
|
| 473 |
+
|
| 474 |
+
f"**Step 1:** State the Goal and the Required Identity.\n"
|
| 475 |
+
f"Our goal is to match the given signal to the canonical bandpass form:\n"
|
| 476 |
+
f"g(t) = g_I(t)*cos(2*pi*fc*t) - g_Q(t)*sin(2*pi*fc*t)\n"
|
| 477 |
+
f"To do this, we use the trigonometric angle addition identity:\n"
|
| 478 |
+
f"cos(a + b) = cos(a)*cos(b) - sin(a)*sin(b)\n\n"
|
| 479 |
+
|
| 480 |
+
f"**Step 2:** Expand the Given Signal Expression.\n"
|
| 481 |
+
f"Let a = 2*pi*fc*t and b = {phase_deg} deg. Applying the identity to g(t):\n"
|
| 482 |
+
f"g(t) = {amplitude} * [cos(2*pi*fc*t)*cos({phase_deg} deg) - sin(2*pi*fc*t)*sin({phase_deg} deg)]\n"
|
| 483 |
+
f"g(t) = ({amplitude}*cos({phase_deg} deg))*cos(2*pi*fc*t) - ({amplitude}*sin({phase_deg} deg))*sin(2*pi*fc*t)\n\n"
|
| 484 |
+
|
| 485 |
+
f"**Step 3:** Identify the In-Phase (g_I) and Quadrature (g_Q) Components.\n"
|
| 486 |
+
f"By comparing our expanded signal to the canonical form, we can identify g_I(t) and g_Q(t):\n"
|
| 487 |
+
f"g_I(t) is the term multiplying cos(2*pi*fc*t).\n"
|
| 488 |
+
f"g_Q(t) is the term multiplying sin(2*pi*fc*t) (note the minus sign is part of the formula).\n\n"
|
| 489 |
+
f"g_I(t) = {amplitude} * cos({phase_deg} deg) = {amplitude} * ({cos_phi:.{precision+1}f}) = {g_I:.{precision+1}f}\n"
|
| 490 |
+
f"g_Q(t) = {amplitude} * sin({phase_deg} deg) = {amplitude} * ({sin_phi:.{precision+1}f}) = {g_Q:.{precision+1}f}\n"
|
| 491 |
+
f"Note that for this signal, g_I and g_Q are constants because the amplitude and phase are constant.\n\n"
|
| 492 |
+
|
| 493 |
+
f"**Step 4:** Construct the Complex Lowpass Equivalent Signal (g_tilde(t)).\n"
|
| 494 |
+
f"The complex lowpass equivalent is defined as g_tilde(t) = g_I(t) + j*g_Q(t).\n"
|
| 495 |
+
f"g_tilde(t) = {round(g_I, precision)} + j*{round(g_Q, precision)}\n\n"
|
| 496 |
+
|
| 497 |
+
f"**Answer:**\n"
|
| 498 |
+
f"In-Phase Component: **g_I(t) = {round(g_I, precision)}**\n"
|
| 499 |
+
f"Quadrature Component: **g_Q(t) = {round(g_Q, precision)}**\n"
|
| 500 |
+
f"Complex Lowpass Equivalent: **g_tilde(t) = {round(g_I, precision)} + j*{round(g_Q, precision)}**"
|
| 501 |
+
)
|
| 502 |
+
|
| 503 |
+
return question, solution
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
def main():
|
| 507 |
+
"""
|
| 508 |
+
Generate numerous instances of each deterministic and random signal analysis template
|
| 509 |
+
with different random seeds and write the results to a JSONL file.
|
| 510 |
+
"""
|
| 511 |
+
import json
|
| 512 |
+
import os
|
| 513 |
+
|
| 514 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 515 |
+
output_file = "testset/electrical_engineering/digital_communications/deterministic_and_random_signal_analysis.jsonl"
|
| 516 |
+
|
| 517 |
+
# Create the directory if it doesn't exist
|
| 518 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 519 |
+
|
| 520 |
+
# List of template functions with their ID and level
|
| 521 |
+
templates = [
|
| 522 |
+
(template_signal_energy_power, "signal_energy_power", "Easy"),
|
| 523 |
+
(template_mean_variance, "mean_variance_discrete_rv", "Easy"),
|
| 524 |
+
(template_autocorrelation_rect_pulse, "autocorrelation_rect_pulse", "Intermediate"),
|
| 525 |
+
(template_ft_esd_rect_pulse, "ft_esd_rect_pulse", "Intermediate"),
|
| 526 |
+
(template_lowpass_equivalent_bandpass, "lowpass_equivalent_bandpass", "Advanced"),
|
| 527 |
+
]
|
| 528 |
+
|
| 529 |
+
# List to store all generated problems
|
| 530 |
+
all_problems = []
|
| 531 |
+
|
| 532 |
+
# Generate problems for each template
|
| 533 |
+
for template_func, id_name, level in templates:
|
| 534 |
+
for _ in range(50):
|
| 535 |
+
# Generate a unique seed for each problem
|
| 536 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 537 |
+
random.seed(seed)
|
| 538 |
+
|
| 539 |
+
# Generate the problem and solution
|
| 540 |
+
question, solution = template_func()
|
| 541 |
+
|
| 542 |
+
# Create a JSON entry
|
| 543 |
+
problem_entry = {
|
| 544 |
+
"seed": seed,
|
| 545 |
+
"branch": "electrical_engineering",
|
| 546 |
+
"domain": "digital_communications",
|
| 547 |
+
"area": "deterministic_and_random_signal_analysis",
|
| 548 |
+
"id": id_name,
|
| 549 |
+
"level": level,
|
| 550 |
+
"question": question,
|
| 551 |
+
"solution": solution
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
# Add to the list of problems
|
| 555 |
+
all_problems.append(problem_entry)
|
| 556 |
+
|
| 557 |
+
# Shuffle the problems to mix templates and levels
|
| 558 |
+
random.shuffle(all_problems)
|
| 559 |
+
|
| 560 |
+
# Write all problems to a .jsonl file
|
| 561 |
+
with open(output_file, "w") as file:
|
| 562 |
+
for problem in all_problems:
|
| 563 |
+
file.write(json.dumps(problem))
|
| 564 |
+
file.write("\n")
|
| 565 |
+
|
| 566 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
if __name__ == "__main__":
|
| 570 |
+
main()
|
data/templates/branches/electrical_engineering/digital_communications/digital_modulation_schemes.py
ADDED
|
@@ -0,0 +1,664 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_bpsk_energy_basis():
|
| 7 |
+
"""
|
| 8 |
+
BPSK/BASK Signal Energy and Basis Function
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template tests the understanding of how to analyze a basic modulated
|
| 12 |
+
signal, which is a fundamental first step in signal space analysis. It
|
| 13 |
+
involves calculating the energy of the signal (Eb) over one bit period
|
| 14 |
+
and deriving its corresponding orthonormal basis function (psi(t)). These
|
| 15 |
+
two components are essential for representing signals as vectors.
|
| 16 |
+
|
| 17 |
+
Core Equations:
|
| 18 |
+
Signal: s(t) = A * cos(2*pi*fc*t)
|
| 19 |
+
Energy Integral: Eb = integral from 0 to Tb of [s(t)]^2 dt
|
| 20 |
+
Simplified Energy: Eb = (A^2 * Tb) / 2
|
| 21 |
+
Basis Function: psi_1(t) = s(t) / sqrt(Eb) = sqrt(2 / Tb) * cos(2*pi*fc*t)
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
tuple: A tuple containing:
|
| 25 |
+
- str: A question about BPSK signal properties.
|
| 26 |
+
- str: A step-by-step solution.
|
| 27 |
+
"""
|
| 28 |
+
# 1. Parameterize the inputs with random values
|
| 29 |
+
|
| 30 |
+
# Set a standard precision for rounding in all calculations and outputs
|
| 31 |
+
precision = 2
|
| 32 |
+
|
| 33 |
+
amplitude = round(random.uniform(1.0, 10.0), precision)
|
| 34 |
+
|
| 35 |
+
# Generate bit duration in a range from 1 microsecond to 10 milliseconds
|
| 36 |
+
bit_duration_s = random.uniform(1e-6, 1e-2)
|
| 37 |
+
|
| 38 |
+
# Choose a carrier frequency that is an integer multiple of the bit rate (1/Tb)
|
| 39 |
+
k = random.randint(2, 20)
|
| 40 |
+
carrier_freq_hz = k / bit_duration_s
|
| 41 |
+
|
| 42 |
+
# --- Start: Inline formatting for bit_duration_s ---
|
| 43 |
+
prefixes = {6: 'M', 3: 'k', 0: '', -3: 'm', -6: 'u', -9: 'n'}
|
| 44 |
+
exponent_td = int(math.floor(math.log10(abs(bit_duration_s)) / 3.0) * 3)
|
| 45 |
+
prefix_td = prefixes.get(exponent_td, '')
|
| 46 |
+
scaled_td = bit_duration_s / (10**exponent_td)
|
| 47 |
+
bit_duration_str = f"{round(scaled_td, precision)} {prefix_td}s"
|
| 48 |
+
# --- End: Inline formatting ---
|
| 49 |
+
|
| 50 |
+
# --- Start: Inline formatting for carrier_freq_hz ---
|
| 51 |
+
exponent_fc = int(math.floor(math.log10(abs(carrier_freq_hz)) / 3.0) * 3)
|
| 52 |
+
prefix_fc = prefixes.get(exponent_fc, '')
|
| 53 |
+
scaled_fc = carrier_freq_hz / (10**exponent_fc)
|
| 54 |
+
carrier_freq_str = f"{round(scaled_fc, precision)} {prefix_fc}Hz"
|
| 55 |
+
# --- End: Inline formatting ---
|
| 56 |
+
|
| 57 |
+
# 2. Perform the core calculation
|
| 58 |
+
|
| 59 |
+
# Calculate energy per bit: Eb = (A^2 * Tb) / 2
|
| 60 |
+
energy_joules = (amplitude**2 * bit_duration_s) / 2
|
| 61 |
+
|
| 62 |
+
# Calculate the amplitude of the basis function: sqrt(2 / Tb)
|
| 63 |
+
basis_amplitude = math.sqrt(2 / bit_duration_s)
|
| 64 |
+
|
| 65 |
+
# --- Start: Inline formatting for energy_joules ---
|
| 66 |
+
exponent_e = int(math.floor(math.log10(abs(energy_joules)) / 3.0) * 3)
|
| 67 |
+
prefix_e = prefixes.get(exponent_e, '')
|
| 68 |
+
scaled_e = energy_joules / (10**exponent_e)
|
| 69 |
+
energy_str = f"{round(scaled_e, precision)} {prefix_e}J"
|
| 70 |
+
# --- End: Inline formatting ---
|
| 71 |
+
|
| 72 |
+
# 3. Generate the question and solution strings
|
| 73 |
+
|
| 74 |
+
question = (
|
| 75 |
+
f"A binary ASK (BASK) signal is defined by the following equation for one bit interval:\n"
|
| 76 |
+
f"s(t) = {amplitude} * cos(2*pi*{carrier_freq_str}*t)\n"
|
| 77 |
+
f"for the time interval 0 <= t <= {bit_duration_str}.\n\n"
|
| 78 |
+
f"Calculate the following:\n"
|
| 79 |
+
f"a) The energy per bit (Eb).\n"
|
| 80 |
+
f"b) The orthonormal basis function psi_1(t)."
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
solution = (
|
| 84 |
+
f"**Given Signal:**\n"
|
| 85 |
+
f"s(t) = {amplitude} * cos(2*pi*{carrier_freq_str}*t) for 0 <= t <= {bit_duration_str}\n\n"
|
| 86 |
+
|
| 87 |
+
f"**Step 1:** Identify Parameters\n"
|
| 88 |
+
f"From the signal definition, we can extract the following parameters:\n"
|
| 89 |
+
f"Amplitude (A): {amplitude} V\n"
|
| 90 |
+
f"Bit Duration (Tb): {bit_duration_str} ({bit_duration_s:.4e} s)\n"
|
| 91 |
+
f"Carrier Frequency (fc): {carrier_freq_str} ({carrier_freq_hz:.4e} Hz)\n\n"
|
| 92 |
+
|
| 93 |
+
f"**Step 2:** Calculate Energy per Bit (Eb)\n"
|
| 94 |
+
f"The energy of a signal is the integral of its squared magnitude over its duration.\n"
|
| 95 |
+
f"Eb = integral from 0 to Tb of [s(t)]^2 dt\n"
|
| 96 |
+
f"For a sinusoidal signal of the form A*cos(...), the energy over an interval Tb simplifies to:\n"
|
| 97 |
+
f"Eb = (A^2 * Tb) / 2\n"
|
| 98 |
+
f"Substituting the values:\n"
|
| 99 |
+
f"Eb = ({amplitude}^2 * {bit_duration_s:.4e}) / 2\n"
|
| 100 |
+
f"Eb = {energy_joules:.4e} J\n"
|
| 101 |
+
f"Eb = {energy_str}\n\n"
|
| 102 |
+
|
| 103 |
+
f"**Step 3:** Determine the Basis Function (psi_1(t))\n"
|
| 104 |
+
f"The orthonormal basis function is found by normalizing the signal by the square root of its energy.\n"
|
| 105 |
+
f"psi_1(t) = s(t) / sqrt(Eb)\n"
|
| 106 |
+
f"We can use the general formula derived from this relationship:\n"
|
| 107 |
+
f"psi_1(t) = sqrt(2 / Tb) * cos(2*pi*fc*t)\n"
|
| 108 |
+
f"First, calculate the amplitude of the basis function:\n"
|
| 109 |
+
f"Amplitude_psi = sqrt(2 / {bit_duration_s:.4e} s) = {round(basis_amplitude, precision)}\n"
|
| 110 |
+
f"Now, construct the full basis function:\n"
|
| 111 |
+
f"psi_1(t) = {round(basis_amplitude, precision)} * cos(2*pi*{carrier_freq_str}*t)\n\n"
|
| 112 |
+
|
| 113 |
+
f"**Answer:**\n"
|
| 114 |
+
f"a) The energy per bit (Eb) is {energy_str}.\n"
|
| 115 |
+
f"b) The basis function (psi_1(t)) is {round(basis_amplitude, precision)} * cos(2*pi*{carrier_freq_str}*t)."
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
return question, solution
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
# Template 2 (Intermediate)
|
| 122 |
+
def template_euclidean_distance_binary():
|
| 123 |
+
"""
|
| 124 |
+
Euclidean Distance for Binary Modulation
|
| 125 |
+
|
| 126 |
+
Scenario:
|
| 127 |
+
This template tests the ability to represent signals as vectors in a signal
|
| 128 |
+
space and calculate the Euclidean distance between them. This distance is a
|
| 129 |
+
key factor in determining the noise immunity of a modulation scheme, as a
|
| 130 |
+
larger distance between signal points implies better performance in the
|
| 131 |
+
presence of noise. The problem covers two fundamental binary schemes:
|
| 132 |
+
antipodal (BPSK) and orthogonal (BFSK).
|
| 133 |
+
|
| 134 |
+
Core Equations:
|
| 135 |
+
For BPSK (antipodal signals):
|
| 136 |
+
s1 = (sqrt(Eb))
|
| 137 |
+
s2 = (-sqrt(Eb))
|
| 138 |
+
d = 2 * sqrt(Eb)
|
| 139 |
+
|
| 140 |
+
For Orthogonal BFSK:
|
| 141 |
+
s1 = (sqrt(Eb), 0)
|
| 142 |
+
s2 = (0, sqrt(Eb))
|
| 143 |
+
d = sqrt(2 * Eb)
|
| 144 |
+
|
| 145 |
+
Returns:
|
| 146 |
+
tuple: A tuple containing:
|
| 147 |
+
- str: A question about Euclidean distance.
|
| 148 |
+
- str: A step-by-step solution.
|
| 149 |
+
"""
|
| 150 |
+
# 1. Parameterize the inputs with random values
|
| 151 |
+
|
| 152 |
+
precision = 3
|
| 153 |
+
modulation_type = random.choice(['BPSK', 'Orthogonal BFSK'])
|
| 154 |
+
|
| 155 |
+
# Generate energy per bit, Eb, in a range from picojoules to nanojoules
|
| 156 |
+
energy_joules = random.uniform(1e-12, 1e-9)
|
| 157 |
+
|
| 158 |
+
# --- Start: Inline formatting for energy_joules ---
|
| 159 |
+
prefixes = {0: '', -3: 'm', -6: 'u', -9: 'n', -12: 'p'}
|
| 160 |
+
if energy_joules > 0:
|
| 161 |
+
exponent_e = int(math.floor(math.log10(abs(energy_joules)) / 3.0) * 3)
|
| 162 |
+
prefix_e = prefixes.get(exponent_e, '')
|
| 163 |
+
scaled_e = energy_joules / (10**exponent_e)
|
| 164 |
+
energy_str = f"{round(scaled_e, precision)} {prefix_e}J"
|
| 165 |
+
else:
|
| 166 |
+
energy_str = "0 J"
|
| 167 |
+
# --- End: Inline formatting ---
|
| 168 |
+
|
| 169 |
+
# 2. Perform the core calculation based on modulation type
|
| 170 |
+
|
| 171 |
+
sqrt_eb = math.sqrt(energy_joules)
|
| 172 |
+
|
| 173 |
+
if modulation_type == 'BPSK':
|
| 174 |
+
# BPSK signals are antipodal (180 degrees apart)
|
| 175 |
+
constellation_type = "antipodal"
|
| 176 |
+
dimension = "one-dimensional"
|
| 177 |
+
basis_count = "one basis function, psi_1(t)"
|
| 178 |
+
|
| 179 |
+
s1_str = f"({sqrt_eb:.{precision}e})"
|
| 180 |
+
s2_str = f"(-{sqrt_eb:.{precision}e})"
|
| 181 |
+
|
| 182 |
+
distance = 2 * sqrt_eb
|
| 183 |
+
|
| 184 |
+
derivation_steps = (
|
| 185 |
+
f"The signal vectors are s1 = (sqrt(Eb)) and s2 = (-sqrt(Eb)).\n"
|
| 186 |
+
f"s1 = ({round(sqrt_eb, precision)})\n"
|
| 187 |
+
f"s2 = (-{round(sqrt_eb, precision)})\n\n"
|
| 188 |
+
|
| 189 |
+
f"**Step 2:** Calculate Euclidean Distance (d)\n"
|
| 190 |
+
f"The distance d is the magnitude of the difference vector (s1 - s2).\n"
|
| 191 |
+
f"s1 - s2 = (sqrt(Eb) - (-sqrt(Eb))) = (2*sqrt(Eb))\n"
|
| 192 |
+
f"d = ||s1 - s2|| = 2*sqrt(Eb)\n"
|
| 193 |
+
f"d = 2 * {round(sqrt_eb, precision)}\n"
|
| 194 |
+
f"d = {round(distance, precision)}"
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
else: # Orthogonal BFSK
|
| 198 |
+
# BFSK signals are orthogonal (90 degrees apart)
|
| 199 |
+
constellation_type = "orthogonal"
|
| 200 |
+
dimension = "two-dimensional"
|
| 201 |
+
basis_count = "two basis functions, psi_1(t) and psi_2(t)"
|
| 202 |
+
|
| 203 |
+
s1_str = f"({sqrt_eb:.{precision}e}, 0)"
|
| 204 |
+
s2_str = f"(0, {sqrt_eb:.{precision}e})"
|
| 205 |
+
|
| 206 |
+
distance = math.sqrt(2 * energy_joules)
|
| 207 |
+
|
| 208 |
+
derivation_steps = (
|
| 209 |
+
f"The signal vectors are s1 = (sqrt(Eb), 0) and s2 = (0, sqrt(Eb)).\n"
|
| 210 |
+
f"s1 = ({round(sqrt_eb, precision)}, 0)\n"
|
| 211 |
+
f"s2 = (0, {round(sqrt_eb, precision)})\n\n"
|
| 212 |
+
|
| 213 |
+
f"**Step 2: ** Calculate Euclidean Distance (d)\n"
|
| 214 |
+
f"The distance d is the magnitude of the difference vector (s1 - s2).\n"
|
| 215 |
+
f"s1 - s2 = (sqrt(Eb) - 0, 0 - sqrt(Eb)) = (sqrt(Eb), -sqrt(Eb))\n"
|
| 216 |
+
f"d = ||s1 - s2|| = sqrt( (sqrt(Eb))^2 + (-sqrt(Eb))^2 ) = sqrt(2*Eb)\n"
|
| 217 |
+
f"d = sqrt(2 * {energy_joules:.{precision}e})\n"
|
| 218 |
+
f"d = {round(distance, precision)}"
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
# 3. Generate the question and solution strings
|
| 222 |
+
|
| 223 |
+
question = (
|
| 224 |
+
f"A digital communication system uses {modulation_type} modulation.\n"
|
| 225 |
+
f"The energy per bit is Eb = {energy_str}.\n\n"
|
| 226 |
+
f"Determine the following:\n"
|
| 227 |
+
f"a) The signal constellation points (s1 and s2) in vector form.\n"
|
| 228 |
+
f"b) The Euclidean distance (d) between these two points."
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
solution = (
|
| 232 |
+
f"**Given Information:**\n"
|
| 233 |
+
f"Modulation Scheme: {modulation_type}\n"
|
| 234 |
+
f"Energy per Bit (Eb): {energy_str} ({energy_joules:.{precision}e} J)\n\n"
|
| 235 |
+
|
| 236 |
+
f"**Step 1:** Define Signal Vectors\n"
|
| 237 |
+
f"For {modulation_type} modulation, the signals are {constellation_type}. This means we can represent them in a {dimension} signal space using {basis_count}.\n"
|
| 238 |
+
f"First, we calculate sqrt(Eb) = sqrt({energy_joules:.{precision}e}) = {round(sqrt_eb, precision)}.\n"
|
| 239 |
+
f"{derivation_steps}\n\n"
|
| 240 |
+
|
| 241 |
+
f"**Answer:**\n"
|
| 242 |
+
f"a) The signal constellation points are:\n"
|
| 243 |
+
f"s1 = {s1_str}\n"
|
| 244 |
+
f"s2 = {s2_str}\n"
|
| 245 |
+
f"b) The Euclidean distance between the points is {round(distance, precision)}."
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
return question, solution
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
# Template 3 (Intermediate)
|
| 252 |
+
def template_average_energy_mqam():
|
| 253 |
+
"""
|
| 254 |
+
Average Energy of an M-QAM Constellation
|
| 255 |
+
|
| 256 |
+
Scenario:
|
| 257 |
+
For designing power-efficient transmitters, understanding the average energy
|
| 258 |
+
per transmitted symbol is crucial. Unlike M-PSK where all symbols have the
|
| 259 |
+
same energy, square M-QAM constellations have symbols with varying energies.
|
| 260 |
+
This template tests the ability to calculate the average symbol energy by
|
| 261 |
+
analyzing the geometry of the constellation.
|
| 262 |
+
|
| 263 |
+
Core Equations:
|
| 264 |
+
Coordinates: (xi, yj) where xi, yj are in {+/-A, +/-3A, ...}
|
| 265 |
+
Symbol Energy: Ei = xi^2 + yj^2
|
| 266 |
+
Average Energy: E_avg = (1/M) * Sum(Ei for all points)
|
| 267 |
+
General Formula: E_avg = (2/3) * (M - 1) * A^2
|
| 268 |
+
|
| 269 |
+
Returns:
|
| 270 |
+
tuple: A tuple containing:
|
| 271 |
+
- str: A question asking for the average energy of an M-QAM constellation.
|
| 272 |
+
- str: A step-by-step solution showing the calculation.
|
| 273 |
+
"""
|
| 274 |
+
# 1. Parameterize the inputs with random values
|
| 275 |
+
precision = 2
|
| 276 |
+
# Add 256-QAM to the list of possible modulation orders
|
| 277 |
+
M = random.choice([4, 16, 64, 256])
|
| 278 |
+
# Randomly decide whether A is an integer or a float for more variety
|
| 279 |
+
if random.choice([True, False]):
|
| 280 |
+
# Generate an integer A
|
| 281 |
+
A = random.randint(1, 10)
|
| 282 |
+
else:
|
| 283 |
+
# Generate a float A from a wider range
|
| 284 |
+
A = round(random.uniform(0.2, 10.0), precision)
|
| 285 |
+
|
| 286 |
+
# 2. Perform the core calculation based on M
|
| 287 |
+
|
| 288 |
+
# The general formula for average energy in a square M-QAM is (2/3)(M-1)A^2
|
| 289 |
+
# We will calculate it directly for the solution steps.
|
| 290 |
+
avg_energy_coeff = (2/3) * (M - 1)
|
| 291 |
+
avg_energy = avg_energy_coeff * (A**2)
|
| 292 |
+
|
| 293 |
+
# Build the solution steps string based on the value of M
|
| 294 |
+
if M == 4:
|
| 295 |
+
coordinate_set = "{+/-A}"
|
| 296 |
+
solution_steps = (
|
| 297 |
+
f"**Step 2:** List Signal Point Energies\n"
|
| 298 |
+
f"For 4-QAM, there is only one type of signal point, located at coordinates (+/-A, +/-A).\n"
|
| 299 |
+
f"There are 4 points, all with energy E = A^2 + A^2 = 2A^2.\n\n"
|
| 300 |
+
|
| 301 |
+
f"**Step 3:** Calculate Average Energy (E_avg)\n"
|
| 302 |
+
f"We sum the energies of all points and divide by M.\n"
|
| 303 |
+
f"Total Energy = 4 * (2 * A^2) = 8 * A^2\n"
|
| 304 |
+
f"E_avg = (Total Energy) / M = (8 * A^2) / 4 = 2 * A^2\n"
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
elif M == 16:
|
| 308 |
+
coordinate_set = "{+/-A, +/-3A}"
|
| 309 |
+
solution_steps = (
|
| 310 |
+
f"**Step 2:** List Signal Point Energies\n"
|
| 311 |
+
f"For 16-QAM, the points can be grouped by their distance from the origin (their energy).\n"
|
| 312 |
+
f"4 points at (+/-A, +/-A) have energy E1 = A^2 + A^2 = 2A^2.\n"
|
| 313 |
+
f"8 points at (+/-A, +/-3A) or (+/-3A, +/-A) have energy E2 = A^2 + (3A)^2 = 10A^2.\n"
|
| 314 |
+
f"4 points at (+/-3A, +/-3A) have energy E3 = (3A)^2 + (3A)^2 = 18A^2.\n\n"
|
| 315 |
+
|
| 316 |
+
f"**Step 3:** Calculate Average Energy (E_avg)\n"
|
| 317 |
+
f"We sum the energies of all points and divide by M.\n"
|
| 318 |
+
f"Total Energy = 4*(2A^2) + 8*(10A^2) + 4*(18A^2) = 8A^2 + 80A^2 + 72A^2 = 160A^2\n"
|
| 319 |
+
f"E_avg = (Total Energy) / M = (160 * A^2) / 16 = 10 * A^2\n"
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
else: # M == 64
|
| 323 |
+
coordinate_set = "{+/-A, +/-3A, +/-5A, +/-7A}"
|
| 324 |
+
total_energy_coeff = 64 * avg_energy_coeff
|
| 325 |
+
solution_steps = (
|
| 326 |
+
"**Step 2:** List Signal Point Energies\n"
|
| 327 |
+
"For 64-QAM, there are many groups of points with the same energy. We list a few examples:\n"
|
| 328 |
+
"The 4 innermost points at (+/-A, +/-A) have energy E = A^2 + A^2 = 2A^2.\n"
|
| 329 |
+
"The 8 points at (+/-A, +/-3A) or (+/-3A, +/-A) have energy E = A^2 + (3A)^2 = 10A^2.\n"
|
| 330 |
+
"The 4 outermost points at (+/-7A, +/-7A) have energy E = (7A)^2 + (7A)^2 = 98A^2.\n"
|
| 331 |
+
"This process is continued for all 64 points.\n\n"
|
| 332 |
+
|
| 333 |
+
"**Step 3:** Calculate Average Energy (E_avg)\n"
|
| 334 |
+
"Summing the energies for all 64 points and dividing by M gives the average. We can also use the general formula for square M-QAM: E_avg = (2/3)*(M-1)*A^2.\n"
|
| 335 |
+
"E_avg = (2/3) * (64 - 1) * A^2\n"
|
| 336 |
+
"E_avg = (2/3) * 63 * A^2 = 42 * A^2\n"
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
# 3. Generate the question and solution strings
|
| 340 |
+
|
| 341 |
+
question = (
|
| 342 |
+
f"For a square {M}-QAM constellation, the signal points are located at (xi, yj) where the coordinates xi and yj are chosen from the set {coordinate_set}.\n\n"
|
| 343 |
+
f"The fundamental distance parameter is A = {A}.\n\n"
|
| 344 |
+
f"Calculate the average energy per symbol (E_avg) for this constellation."
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
solution = (
|
| 348 |
+
f"**Given Information:**\n"
|
| 349 |
+
f"Modulation Scheme: {M}-QAM\n"
|
| 350 |
+
f"Distance Parameter (A): {A}\n\n"
|
| 351 |
+
|
| 352 |
+
f"**Step 1:** Understand the Constellation Structure\n"
|
| 353 |
+
f"The constellation is a square grid where coordinates are odd multiples of A. The energy of any point (x, y) is simply x^2 + y^2.\n\n"
|
| 354 |
+
|
| 355 |
+
f"{solution_steps}"
|
| 356 |
+
|
| 357 |
+
f"**Step 4:** Final Calculation\n"
|
| 358 |
+
f"Now, we substitute the value of A = {A}.\n"
|
| 359 |
+
f"E_avg = {round(avg_energy_coeff, precision)} * ({A})^2\n"
|
| 360 |
+
f"E_avg = {round(avg_energy_coeff, precision)} * {round(A**2, precision)}\n"
|
| 361 |
+
f"E_avg = {round(avg_energy, precision)}\n\n"
|
| 362 |
+
|
| 363 |
+
f"**Answer:**\n"
|
| 364 |
+
f"The average energy per symbol is {round(avg_energy, precision)}."
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
return question, solution
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
# Template 4 (Advanced)
|
| 371 |
+
def template_ber_estimation_mary():
|
| 372 |
+
"""
|
| 373 |
+
Bit Error Rate (BER) Estimation for M-ary Schemes
|
| 374 |
+
|
| 375 |
+
Scenario:
|
| 376 |
+
Predicting the performance of a digital communication system in the presence of
|
| 377 |
+
noise is a critical engineering task. This template tests the ability to
|
| 378 |
+
estimate the bit error rate (BER) from a given signal-to-noise ratio per
|
| 379 |
+
bit (Eb/N0) for common M-ary modulation schemes. It requires using standard
|
| 380 |
+
approximation formulas that are expressed in terms of the complementary
|
| 381 |
+
Gaussian distribution function, Q(x).
|
| 382 |
+
|
| 383 |
+
Core Equations:
|
| 384 |
+
Linear SNR: (Eb/N0)_lin = 10^((Eb/N0)_dB / 10)
|
| 385 |
+
Bits per symbol: k = log2(M)
|
| 386 |
+
M-PSK SER: Ps approx 2 * Q(sqrt(2 * Es/N0) * sin(pi/M))
|
| 387 |
+
M-QAM SER: Ps approx 4 * (1 - 1/sqrt(M)) * Q(sqrt(3*k/(M-1) * Eb/N0))
|
| 388 |
+
Gray Code BER: Pb approx Ps / k
|
| 389 |
+
|
| 390 |
+
Returns:
|
| 391 |
+
tuple: A tuple containing:
|
| 392 |
+
- str: A question asking for the BER estimation.
|
| 393 |
+
- str: A step-by-step solution showing the calculation.
|
| 394 |
+
"""
|
| 395 |
+
# 1. Enhanced Parameter Randomization
|
| 396 |
+
precision = random.randint(1, 2)
|
| 397 |
+
modulation_scheme = random.choice(['M-PSK', 'M-QAM'])
|
| 398 |
+
|
| 399 |
+
if modulation_scheme == 'M-PSK':
|
| 400 |
+
M = random.choice([4, 8, 16, 32])
|
| 401 |
+
else: # M-QAM
|
| 402 |
+
# 4-QAM is identical to 4-PSK (QPSK), so we start at 16 for variety.
|
| 403 |
+
M = random.choice([16, 64, 256])
|
| 404 |
+
|
| 405 |
+
eb_n0_db = round(random.uniform(5.0, 25.0), precision)
|
| 406 |
+
|
| 407 |
+
# 2. Perform the core calculation
|
| 408 |
+
eb_n0_lin = 10**(eb_n0_db / 10)
|
| 409 |
+
k = math.log2(M)
|
| 410 |
+
|
| 411 |
+
# Logic varies based on modulation scheme
|
| 412 |
+
if modulation_scheme == 'M-PSK':
|
| 413 |
+
# For M-PSK, the formula uses Es/N0
|
| 414 |
+
es_n0_lin = k * eb_n0_lin
|
| 415 |
+
q_arg = math.sqrt(2 * es_n0_lin) * math.sin(math.pi / M)
|
| 416 |
+
ser_coeff = 2
|
| 417 |
+
ber_coeff = ser_coeff / k
|
| 418 |
+
|
| 419 |
+
ser_approx_str = f"{ser_coeff} * Q({q_arg:.{precision+1}f})"
|
| 420 |
+
ber_approx_str = f"{ber_coeff:.{precision+1}f} * Q({q_arg:.{precision+1}f})"
|
| 421 |
+
|
| 422 |
+
calculation_steps = (
|
| 423 |
+
f"**Step 2:** Calculate Symbol SNR (Es/N0)\n"
|
| 424 |
+
f"The number of bits per symbol is k = log2({M}) = {k:.0f}.\n"
|
| 425 |
+
f"The energy per symbol (Es) is k times the energy per bit (Eb).\n"
|
| 426 |
+
f"Es/N0 = k * (Eb/N0) = {k:.0f} * {eb_n0_lin:.{precision}f} = {es_n0_lin:.{precision}f}\n\n"
|
| 427 |
+
|
| 428 |
+
f"**Step 3:** Calculate Symbol Error Rate (SER)\n"
|
| 429 |
+
f"For M-PSK, the SER is approximated by: Ps approx 2*Q(sqrt(2*Es/N0)*sin(pi/M)).\n"
|
| 430 |
+
f"First, calculate the argument of the Q-function:\n"
|
| 431 |
+
f"arg = sqrt(2 * {es_n0_lin:.{precision}f}) * sin(pi / {M})\n"
|
| 432 |
+
f"arg = {q_arg:.{precision+1}f}\n"
|
| 433 |
+
f"So, the SER is: Ps approx {ser_approx_str}\n"
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
else: # M-QAM
|
| 437 |
+
# For square M-QAM, the formula directly uses Eb/N0
|
| 438 |
+
q_arg = math.sqrt((3 * k / (M - 1)) * eb_n0_lin)
|
| 439 |
+
ser_coeff = 4 * (1 - 1/math.sqrt(M))
|
| 440 |
+
ber_coeff = ser_coeff / k
|
| 441 |
+
|
| 442 |
+
ser_approx_str = f"{ser_coeff:.{precision+1}f} * Q({q_arg:.{precision+1}f})"
|
| 443 |
+
ber_approx_str = f"{ber_coeff:.{precision+1}f} * Q({q_arg:.{precision+1}f})"
|
| 444 |
+
|
| 445 |
+
calculation_steps = (
|
| 446 |
+
f"**Step 2:** Calculate Bits per Symbol (k)\n"
|
| 447 |
+
f"The number of bits per symbol is k = log2({M}) = {k:.0f}.\n\n"
|
| 448 |
+
|
| 449 |
+
f"**Step 3:** Calculate Symbol Error Rate (SER)\n"
|
| 450 |
+
f"For square M-QAM, the SER is approximated by: Ps approx 4*(1-1/sqrt(M))*Q(sqrt(3*k/(M-1) * Eb/N0)).\n"
|
| 451 |
+
f"First, calculate the argument of the Q-function:\n"
|
| 452 |
+
f"arg = sqrt( (3*{k:.0f} / ({M}-1)) * {eb_n0_lin:.{precision}f} )\n"
|
| 453 |
+
f"arg = {q_arg:.{precision+1}f}\n"
|
| 454 |
+
f"So, the SER is: Ps approx {ser_approx_str}\n"
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
# 3. Generate the question and solution strings
|
| 458 |
+
question = (
|
| 459 |
+
f"A digital communication system uses {M}-{modulation_scheme.split('-')[1]} modulation over an AWGN channel.\n"
|
| 460 |
+
f"The system operates with a signal-to-noise ratio per bit of Eb/N0 = {eb_n0_db} dB.\n\n"
|
| 461 |
+
"Assuming a Gray code mapping is used, provide a mathematical expression for the estimated Bit Error Rate (BER)."
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
solution = (
|
| 465 |
+
f"**Given Information:**\n"
|
| 466 |
+
f"Modulation: {M}-{modulation_scheme.split('-')[1]}\n"
|
| 467 |
+
f"Eb/N0: {eb_n0_db} dB\n\n"
|
| 468 |
+
|
| 469 |
+
f"**Step 1:** Convert SNR from dB to Linear Scale\n"
|
| 470 |
+
f"The linear value of Eb/N0 is required for the error rate formulas.\n"
|
| 471 |
+
f"Eb/N0 (linear) = 10^( ({eb_n0_db}) / 10 ) = {eb_n0_lin:.{precision}f}\n\n"
|
| 472 |
+
|
| 473 |
+
f"{calculation_steps}\n"
|
| 474 |
+
|
| 475 |
+
f"**Step 4:** Approximate Bit Error Rate (BER)\n"
|
| 476 |
+
f"For Gray-coded constellations, the BER can be approximated from the SER by Pb approx Ps / k.\n"
|
| 477 |
+
f"Pb approx ({ser_approx_str}) / {k:.0f}\n"
|
| 478 |
+
f"Pb approx {ber_approx_str}\n\n"
|
| 479 |
+
|
| 480 |
+
f"**Answer:**\n"
|
| 481 |
+
f"The estimated Bit Error Rate (BER) is expressed as:\n"
|
| 482 |
+
f"BER approx {ber_approx_str}"
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
return question, solution
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
# Template 5 (Advanced)
|
| 489 |
+
def template_null_to_null_bandwidth():
|
| 490 |
+
"""
|
| 491 |
+
Null-to-Null Bandwidth Calculation
|
| 492 |
+
|
| 493 |
+
Scenario:
|
| 494 |
+
Understanding the bandwidth requirements of a signal is essential for designing
|
| 495 |
+
and analyzing communication systems. The null-to-null bandwidth of the main
|
| 496 |
+
spectral lobe is a fundamental, albeit theoretical, measure of the spectrum
|
| 497 |
+
occupied by a signal shaped with basic rectangular pulses. This template tests
|
| 498 |
+
the ability to calculate this bandwidth by relating the user data rate (Rb)
|
| 499 |
+
to the transmission symbol rate (Rs) for various M-ary modulation schemes.
|
| 500 |
+
|
| 501 |
+
Core Equations:
|
| 502 |
+
Bits per Symbol: k = log2(M)
|
| 503 |
+
Symbol Rate: Rs = Rb / k
|
| 504 |
+
Null-to-Null RF Bandwidth: B_null = 2 * Rs
|
| 505 |
+
|
| 506 |
+
Returns:
|
| 507 |
+
tuple: A tuple containing:
|
| 508 |
+
- str: A question asking for the null-to-null bandwidth.
|
| 509 |
+
- str: A step-by-step solution showing the calculation.
|
| 510 |
+
"""
|
| 511 |
+
# 1. Enhanced Parameter Randomization
|
| 512 |
+
precision = 2
|
| 513 |
+
modulation_scheme = random.choice(['M-PSK', 'M-QAM', 'M-PAM'])
|
| 514 |
+
|
| 515 |
+
# Choose M from a scheme-appropriate set
|
| 516 |
+
if modulation_scheme == 'M-PSK':
|
| 517 |
+
M = random.choice([2, 4, 8, 16, 32, 64, 128])
|
| 518 |
+
elif modulation_scheme == 'M-QAM':
|
| 519 |
+
M = random.choice([4, 16, 32, 64, 128, 256, 512, 1024])
|
| 520 |
+
else: # M-PAM
|
| 521 |
+
M = random.choice([2, 4, 8, 16, 32, 64])
|
| 522 |
+
|
| 523 |
+
# Randomize bit rate value and unit for diversity
|
| 524 |
+
unit_choice = random.choice(['kbps', 'Mbps', 'Gbps'])
|
| 525 |
+
if unit_choice == 'kbps':
|
| 526 |
+
value = random.randint(100, 999)
|
| 527 |
+
multiplier = 1e3
|
| 528 |
+
elif unit_choice == 'Mbps':
|
| 529 |
+
value = random.choice([1, 1.5, 2, 5, 10, 25, 50, 100])
|
| 530 |
+
multiplier = 1e6
|
| 531 |
+
else: # Gbps
|
| 532 |
+
value = random.choice([1, 2.5, 10])
|
| 533 |
+
multiplier = 1e9
|
| 534 |
+
|
| 535 |
+
bit_rate_str = f"{value} {unit_choice}"
|
| 536 |
+
bit_rate_hz = value * multiplier
|
| 537 |
+
|
| 538 |
+
# 2. Perform the core calculation
|
| 539 |
+
k = math.log2(M)
|
| 540 |
+
symbol_rate_rs = bit_rate_hz / k
|
| 541 |
+
bandwidth_hz = 2 * symbol_rate_rs
|
| 542 |
+
|
| 543 |
+
# --- Start: Inline formatting for bandwidth_hz ---
|
| 544 |
+
prefixes = {12: 'T', 9: 'G', 6: 'M', 3: 'k', 0: ''}
|
| 545 |
+
if bandwidth_hz > 0:
|
| 546 |
+
exponent_b = int(math.floor(math.log10(abs(bandwidth_hz)) / 3.0) * 3)
|
| 547 |
+
prefix_b = prefixes.get(exponent_b, '')
|
| 548 |
+
scaled_b = bandwidth_hz / (10**exponent_b)
|
| 549 |
+
bandwidth_str = f"{round(scaled_b, precision)} {prefix_b}Hz"
|
| 550 |
+
else:
|
| 551 |
+
bandwidth_str = "0 Hz"
|
| 552 |
+
# --- End: Inline formatting ---
|
| 553 |
+
|
| 554 |
+
# --- Start: Inline formatting for symbol_rate_rs ---
|
| 555 |
+
if symbol_rate_rs > 0:
|
| 556 |
+
exponent_rs = int(math.floor(math.log10(abs(symbol_rate_rs)) / 3.0) * 3)
|
| 557 |
+
prefix_rs = prefixes.get(exponent_rs, '')
|
| 558 |
+
scaled_rs = symbol_rate_rs / (10**exponent_rs)
|
| 559 |
+
symbol_rate_str = f"{round(scaled_rs, precision)} {prefix_rs}symbols/s"
|
| 560 |
+
else:
|
| 561 |
+
symbol_rate_str = "0 symbols/s"
|
| 562 |
+
# --- End: Inline formatting ---
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
# 3. Generate the question and solution strings
|
| 566 |
+
question = (
|
| 567 |
+
f"A digital communication system transmits data at a rate of {bit_rate_str} using {M}-{modulation_scheme.split('-')[1]} modulation.\n\n"
|
| 568 |
+
"Assuming the signal is shaped with rectangular baseband pulses, what is the null-to-null RF bandwidth of the transmitted signal's main spectral lobe?"
|
| 569 |
+
)
|
| 570 |
+
|
| 571 |
+
solution = (
|
| 572 |
+
f"**Given Information:**\n"
|
| 573 |
+
f"Modulation: {M}-{modulation_scheme.split('-')[1]}\n"
|
| 574 |
+
f"Bit Rate (Rb): {bit_rate_str} ({bit_rate_hz:.2e} bps)\n\n"
|
| 575 |
+
|
| 576 |
+
f"**Step 1:** Calculate Bits per Symbol (k)\n"
|
| 577 |
+
f"This value determines how many bits are grouped together to form a single symbol.\n"
|
| 578 |
+
f"k = log2(M) = log2({M}) = {k:.0f} bits/symbol\n\n"
|
| 579 |
+
|
| 580 |
+
f"**Step 2:** Calculate Symbol Rate (Rs)\n"
|
| 581 |
+
f"The symbol rate (or baud rate) is the rate at which symbols are transmitted. It is found by dividing the bit rate by the number of bits per symbol.\n"
|
| 582 |
+
f"Rs = Rb / k\n"
|
| 583 |
+
f"Rs = {bit_rate_hz:.2e} bps / {k:.0f} bits/symbol\n"
|
| 584 |
+
f"Rs = {symbol_rate_rs:.2e} symbols/s = {symbol_rate_str}\n\n"
|
| 585 |
+
|
| 586 |
+
f"**Step 3:** Calculate Null-to-Null Bandwidth (B_null)\n"
|
| 587 |
+
f"For a signal using rectangular pulses of duration Ts, the baseband spectrum is a sinc function with its first null at frequency 1/Ts. The symbol rate Rs is equal to 1/Ts.\n"
|
| 588 |
+
f"For a passband RF signal, the main lobe bandwidth is twice the baseband null frequency.\n"
|
| 589 |
+
f"B_null = 2 * (1/Ts) = 2 * Rs\n"
|
| 590 |
+
f"B_null = 2 * {symbol_rate_rs:.2e} symbols/s\n"
|
| 591 |
+
f"B_null = {bandwidth_hz:.2e} Hz = {bandwidth_str}\n\n"
|
| 592 |
+
|
| 593 |
+
f"**Answer:**\n"
|
| 594 |
+
f"The null-to-null RF bandwidth is {bandwidth_str}."
|
| 595 |
+
)
|
| 596 |
+
|
| 597 |
+
return question, solution
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
def main():
|
| 601 |
+
"""
|
| 602 |
+
Generate numerous instances of each digital modulation schemes template
|
| 603 |
+
with different random seeds and write the results to a JSONL file.
|
| 604 |
+
"""
|
| 605 |
+
import json
|
| 606 |
+
import os
|
| 607 |
+
|
| 608 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 609 |
+
output_file = "testset/electrical_engineering/digital_communications/digital_modulation_schemes.jsonl"
|
| 610 |
+
|
| 611 |
+
# Create the directory if it doesn't exist
|
| 612 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 613 |
+
|
| 614 |
+
# List of template functions with their ID and level
|
| 615 |
+
templates = [
|
| 616 |
+
(template_bpsk_energy_basis, "bpsk_energy_basis", "Easy"),
|
| 617 |
+
(template_euclidean_distance_binary, "euclidean_distance_binary", "Intermediate"),
|
| 618 |
+
(template_average_energy_mqam, "average_energy_mqam", "Intermediate"),
|
| 619 |
+
(template_ber_estimation_mary, "ber_estimation_mary", "Advanced"),
|
| 620 |
+
(template_null_to_null_bandwidth, "null_to_null_bandwidth", "Advanced"),
|
| 621 |
+
]
|
| 622 |
+
|
| 623 |
+
# List to store all generated problems
|
| 624 |
+
all_problems = []
|
| 625 |
+
|
| 626 |
+
# Generate problems for each template
|
| 627 |
+
for template_func, id_name, level in templates:
|
| 628 |
+
for _ in range(50):
|
| 629 |
+
# Generate a unique seed for each problem
|
| 630 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 631 |
+
random.seed(seed)
|
| 632 |
+
|
| 633 |
+
# Generate the problem and solution
|
| 634 |
+
question, solution = template_func()
|
| 635 |
+
|
| 636 |
+
# Create a JSON entry
|
| 637 |
+
problem_entry = {
|
| 638 |
+
"seed": seed,
|
| 639 |
+
"branch": "electrical_engineering",
|
| 640 |
+
"domain": "digital_communications",
|
| 641 |
+
"area": "digital_modulation_schemes",
|
| 642 |
+
"id": id_name,
|
| 643 |
+
"level": level,
|
| 644 |
+
"question": question,
|
| 645 |
+
"solution": solution
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
# Add to the list of problems
|
| 649 |
+
all_problems.append(problem_entry)
|
| 650 |
+
|
| 651 |
+
# Shuffle the problems to mix templates and levels
|
| 652 |
+
random.shuffle(all_problems)
|
| 653 |
+
|
| 654 |
+
# Write all problems to a .jsonl file
|
| 655 |
+
with open(output_file, "w") as file:
|
| 656 |
+
for problem in all_problems:
|
| 657 |
+
file.write(json.dumps(problem))
|
| 658 |
+
file.write("\n")
|
| 659 |
+
|
| 660 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 661 |
+
|
| 662 |
+
|
| 663 |
+
if __name__ == "__main__":
|
| 664 |
+
main()
|
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/electrostatics.cpython-311.pyc
ADDED
|
Binary file (30.1 kB). View file
|
|
|
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/magnetostatics.cpython-311.pyc
ADDED
|
Binary file (9.53 kB). View file
|
|
|
data/templates/branches/electrical_engineering/electromagnetics_and_waves/__pycache__/waves_and_phasors.cpython-311.pyc
ADDED
|
Binary file (33.8 kB). View file
|
|
|
data/templates/branches/electrical_engineering/electromagnetics_and_waves/electrostatics.py
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
import numpy as np
|
| 4 |
+
from data.templates.branches.electrical_engineering.constants import EPSILON_0
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# Template 1 (Easy)
|
| 8 |
+
def template_coulombs_law():
|
| 9 |
+
"""
|
| 10 |
+
Coulomb's Law for Two Point Charges
|
| 11 |
+
|
| 12 |
+
Scenario:
|
| 13 |
+
This template tests the fundamental calculation of the electrostatic force
|
| 14 |
+
between two point charges in a dielectric medium. It requires finding the
|
| 15 |
+
separation vector between the charges and applying the vector form of
|
| 16 |
+
Coulomb's Law.
|
| 17 |
+
|
| 18 |
+
Core Equations:
|
| 19 |
+
R_vec = P2 - P1
|
| 20 |
+
F_vec = (1 / (4 * pi * epsilon_0 * epsilon_r)) * (q1 * q2 / |R_vec|^3) * R_vec
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
tuple: A tuple containing:
|
| 24 |
+
- str: A question asking for the force vector between two charges.
|
| 25 |
+
- str: A step-by-step solution showing the calculation.
|
| 26 |
+
"""
|
| 27 |
+
# 1. Parameterize the inputs with random values
|
| 28 |
+
|
| 29 |
+
# Generate random integer charges between -15 and 15 uC (excluding 0)
|
| 30 |
+
q1_val = random.choice([i for i in range(-15, 16) if i != 0])
|
| 31 |
+
q2_val = random.choice([i for i in range(-15, 16) if i != 0])
|
| 32 |
+
|
| 33 |
+
# Convert from microcoulombs to coulombs for calculation
|
| 34 |
+
q1_calc = q1_val * 1e-6
|
| 35 |
+
q2_calc = q2_val * 1e-6
|
| 36 |
+
|
| 37 |
+
# Generate two unique 3D points with integer coordinates from -5 to 5
|
| 38 |
+
p1 = np.array([random.randint(-5, 5) for _ in range(3)])
|
| 39 |
+
p2 = np.array([random.randint(-5, 5) for _ in range(3)])
|
| 40 |
+
while np.array_equal(p1, p2):
|
| 41 |
+
p2 = np.array([random.randint(-5, 5) for _ in range(3)])
|
| 42 |
+
|
| 43 |
+
# Choose a relative permittivity for the medium
|
| 44 |
+
epsilon_r = round(random.uniform(1.0, 5.0), 2)
|
| 45 |
+
|
| 46 |
+
# Standardize precision for final outputs
|
| 47 |
+
precision = 3
|
| 48 |
+
|
| 49 |
+
# 2. Perform the core calculation
|
| 50 |
+
|
| 51 |
+
# Calculate the separation vector and its magnitude
|
| 52 |
+
R_vec = p2 - p1
|
| 53 |
+
R_mag = np.linalg.norm(R_vec)
|
| 54 |
+
|
| 55 |
+
# Calculate the full permittivity of the medium
|
| 56 |
+
epsilon = epsilon_r * EPSILON_0
|
| 57 |
+
|
| 58 |
+
# Calculate the scalar part of Coulomb's Law
|
| 59 |
+
scalar_coefficient = (q1_calc * q2_calc) / (4 * math.pi * epsilon * (R_mag**3))
|
| 60 |
+
|
| 61 |
+
# Calculate the final force vector
|
| 62 |
+
F_vec = scalar_coefficient * R_vec
|
| 63 |
+
|
| 64 |
+
# 3. Generate the question and solution strings
|
| 65 |
+
|
| 66 |
+
question = (
|
| 67 |
+
f"Two point charges are located in a medium with a relative permittivity of {epsilon_r}.\n"
|
| 68 |
+
f"Charge q1 = {q1_val} uC is at position P1 = {p1} m.\n"
|
| 69 |
+
f"Charge q2 = {q2_val} uC is at position P2 = {p2} m.\n\n"
|
| 70 |
+
f"Determine the electrostatic force vector F12 exerted by charge q1 on charge q2."
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
solution = (
|
| 74 |
+
f"**Given:**\n"
|
| 75 |
+
f" - Charge q1 = {q1_val} uC = {q1_val}e-6 C at P1 = {p1} m\n"
|
| 76 |
+
f" - Charge q2 = {q2_val} uC = {q2_val}e-6 C at P2 = {p2} m\n"
|
| 77 |
+
f" - Relative permittivity epsilon_r = {epsilon_r}\n\n"
|
| 78 |
+
f"**Constants:**\n"
|
| 79 |
+
f" - Permittivity of free space, epsilon_0 = {EPSILON_0:.4e} F/m\n\n"
|
| 80 |
+
|
| 81 |
+
f"**Step 1:** Find the separation vector from q1 to q2.\n"
|
| 82 |
+
f" The vector R12 points from P1 to P2.\n"
|
| 83 |
+
f" R12 = P2 - P1 = {p2} - {p1} = {R_vec} m.\n\n"
|
| 84 |
+
|
| 85 |
+
f"**Step 2:** Calculate the magnitude of the separation vector.\n"
|
| 86 |
+
f" |R12| = sqrt({R_vec[0]}^2 + {R_vec[1]}^2 + {R_vec[2]}^2)\n"
|
| 87 |
+
f" |R12| = sqrt({R_vec[0]**2} + {R_vec[1]**2} + {R_vec[2]**2}) = sqrt({R_mag**2:.2f}) = {R_mag:.{precision}f} m.\n\n"
|
| 88 |
+
|
| 89 |
+
f"**Step 3:** Apply the vector form of Coulomb's Law.\n"
|
| 90 |
+
f" The formula for the force F12 is:\n"
|
| 91 |
+
f" F12 = (1 / (4 * pi * epsilon)) * (q1 * q2 / |R12|^3) * R12\n"
|
| 92 |
+
f" where epsilon = epsilon_r * epsilon_0.\n\n"
|
| 93 |
+
|
| 94 |
+
f" First, calculate the total permittivity:\n"
|
| 95 |
+
f" epsilon = {epsilon_r} * {EPSILON_0:.4e} = {epsilon:.4e} F/m.\n\n"
|
| 96 |
+
|
| 97 |
+
f" Now, substitute all values into the formula:\n"
|
| 98 |
+
f" F12 = (1 / (4 * pi * {epsilon:.4e})) * (({q1_calc:.2e}) * ({q2_calc:.2e}) / ({R_mag:.{precision}f})^3) * {R_vec}\n"
|
| 99 |
+
f" F12 = ({1/(4 * math.pi * epsilon):.3e}) * ({(q1_calc * q2_calc):.3e} / {R_mag**3:.3e}) * {R_vec}\n"
|
| 100 |
+
f" F12 = ({scalar_coefficient:.3e}) * {R_vec}\n"
|
| 101 |
+
f" F12 = <{F_vec[0]:.{precision}e}, {F_vec[1]:.{precision}e}, {F_vec[2]:.{precision}e}> N.\n\n"
|
| 102 |
+
|
| 103 |
+
f"**Answer:**\n"
|
| 104 |
+
f" The electrostatic force vector exerted by q1 on q2 is "
|
| 105 |
+
f"<{F_vec[0]:.{precision}e}, {F_vec[1]:.{precision}e}, {F_vec[2]:.{precision}e}> N."
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
return question, solution
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
# Template 2 (Intermediate)
|
| 112 |
+
def template_superposition_electric_field():
|
| 113 |
+
"""
|
| 114 |
+
Superposition of Electric Fields (Intermediate)
|
| 115 |
+
|
| 116 |
+
Scenario:
|
| 117 |
+
This template requires calculating the net electric field at a target
|
| 118 |
+
point due to two or three discrete point charges. It builds on the
|
| 119 |
+
basic E-field calculation by applying the principle of superposition,
|
| 120 |
+
which involves vector addition. The problem is constrained to 2D
|
| 121 |
+
to keep the vector calculations straightforward.
|
| 122 |
+
|
| 123 |
+
Core Equations:
|
| 124 |
+
E_i = (1 / (4 * pi * epsilon_0)) * (q_i / |R_i|^3) * R_i
|
| 125 |
+
E_net = E_1 + E_2 + ...
|
| 126 |
+
|
| 127 |
+
Returns:
|
| 128 |
+
tuple: A tuple containing:
|
| 129 |
+
- str: A question asking for the net electric field at a point.
|
| 130 |
+
- str: A step-by-step solution showing the calculation for each charge
|
| 131 |
+
and the final vector sum.
|
| 132 |
+
"""
|
| 133 |
+
# 1. Parameterize the inputs with random values
|
| 134 |
+
|
| 135 |
+
num_charges = random.choice([2, 3])
|
| 136 |
+
charges_val = []
|
| 137 |
+
positions = []
|
| 138 |
+
|
| 139 |
+
# Use a set to ensure all points (charges and target) are unique
|
| 140 |
+
used_points = set()
|
| 141 |
+
|
| 142 |
+
for i in range(num_charges):
|
| 143 |
+
# Generate random integer charges between -25 and 25 nC (excluding 0)
|
| 144 |
+
q_val = random.choice([i for i in range(-25, 26) if i != 0])
|
| 145 |
+
charges_val.append(q_val)
|
| 146 |
+
|
| 147 |
+
# Generate a unique 2D point for each charge
|
| 148 |
+
while True:
|
| 149 |
+
pos = tuple(random.randint(-10, 10) for _ in range(2))
|
| 150 |
+
if pos not in used_points:
|
| 151 |
+
positions.append(np.array(pos))
|
| 152 |
+
used_points.add(pos)
|
| 153 |
+
break
|
| 154 |
+
|
| 155 |
+
# Generate a unique 2D target point
|
| 156 |
+
while True:
|
| 157 |
+
target_pos = tuple(random.randint(-10, 10) for _ in range(2))
|
| 158 |
+
if target_pos not in used_points:
|
| 159 |
+
P_target = np.array(target_pos)
|
| 160 |
+
used_points.add(target_pos)
|
| 161 |
+
break
|
| 162 |
+
|
| 163 |
+
# Convert from nanocoulombs to coulombs for calculation
|
| 164 |
+
charges_calc = [q * 1e-9 for q in charges_val]
|
| 165 |
+
|
| 166 |
+
# Standardize precision for final outputs
|
| 167 |
+
precision = 2
|
| 168 |
+
|
| 169 |
+
# 2. Perform the core calculation
|
| 170 |
+
|
| 171 |
+
E_net = np.array([0.0, 0.0])
|
| 172 |
+
solution_steps = ""
|
| 173 |
+
|
| 174 |
+
for i in range(num_charges):
|
| 175 |
+
q_calc = charges_calc[i]
|
| 176 |
+
q_val = charges_val[i]
|
| 177 |
+
pos = positions[i]
|
| 178 |
+
|
| 179 |
+
# Calculate separation vector and its magnitude
|
| 180 |
+
R_vec = P_target - pos
|
| 181 |
+
R_mag = np.linalg.norm(R_vec)
|
| 182 |
+
|
| 183 |
+
# Calculate the E-field vector from this charge
|
| 184 |
+
scalar_part = q_calc / (4 * math.pi * EPSILON_0 * R_mag**3)
|
| 185 |
+
E_vec = scalar_part * R_vec
|
| 186 |
+
|
| 187 |
+
# Add to the net field
|
| 188 |
+
E_net += E_vec
|
| 189 |
+
|
| 190 |
+
# Build the solution string for this step
|
| 191 |
+
solution_steps += (
|
| 192 |
+
f"**Step {i+1}:** Calculate the Electric Field from q{i+1} ({q_val} nC).\n"
|
| 193 |
+
f" The separation vector from q{i+1} to the target point P is:\n"
|
| 194 |
+
f" R{i+1} = P - P{i+1} = {P_target} - {pos} = {R_vec} m.\n"
|
| 195 |
+
f" The magnitude is |R{i+1}| = sqrt({R_vec[0]**2} + {R_vec[1]**2}) = {R_mag:.{precision+1}f} m.\n"
|
| 196 |
+
f" The electric field E{i+1} is given by E = (k * q / |R|^3) * R, where k = 1/(4*pi*eps0).\n"
|
| 197 |
+
f" E{i+1} = ({1/(4*math.pi*EPSILON_0):.2e}) * ({q_calc:.2e} / {R_mag:.{precision+1}f}^3) * {R_vec}\n"
|
| 198 |
+
f" E{i+1} = <{E_vec[0]:.{precision}e}, {E_vec[1]:.{precision}e}> N/C.\n\n"
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# 3. Generate the question and solution strings
|
| 202 |
+
|
| 203 |
+
charge_descriptions = ""
|
| 204 |
+
for i in range(num_charges):
|
| 205 |
+
charge_descriptions += f" - Charge q{i+1} = {charges_val[i]} nC is at position P{i+1} = {positions[i]} m.\n"
|
| 206 |
+
|
| 207 |
+
question = (
|
| 208 |
+
f"Several point charges are located in a vacuum on a 2D plane:\n"
|
| 209 |
+
f"{charge_descriptions}"
|
| 210 |
+
f"An observation point is located at P = {P_target} m.\n\n"
|
| 211 |
+
f"Using the principle of superposition, determine the net electric field vector E_net at point P."
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
solution = (
|
| 215 |
+
f"**Given:**\n"
|
| 216 |
+
f"{charge_descriptions}"
|
| 217 |
+
f" - Observation point P = {P_target} m.\n"
|
| 218 |
+
f" - The medium is a vacuum (epsilon_r = 1.0).\n\n"
|
| 219 |
+
f"**Principle of Superposition:**\n"
|
| 220 |
+
f" The total electric field at a point is the vector sum of the electric fields produced by each individual charge.\n"
|
| 221 |
+
f" E_net = E1 + E2{' + E3' if num_charges == 3 else ''}\n\n"
|
| 222 |
+
|
| 223 |
+
f"{solution_steps}"
|
| 224 |
+
f"**Step {num_charges+1}:** Sum the vectors to find the net electric field.\n"
|
| 225 |
+
f" E_net = E1 + E2{' + E3' if num_charges == 3 else ''}\n"
|
| 226 |
+
f" E_net = <E1_x + E2_x{' + E3_x' if num_charges == 3 else ''}, E1_y + E2_y{' + E3_y' if num_charges == 3 else ''}>\n"
|
| 227 |
+
f" E_net = <{E_net[0]:.{precision}e}, {E_net[1]:.{precision}e}> N/C.\n\n"
|
| 228 |
+
|
| 229 |
+
f"**Answer:**\n"
|
| 230 |
+
f" The net electric field vector at point P is <{E_net[0]:.{precision}e}, {E_net[1]:.{precision}e}> N/C."
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
return question, solution
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
# Template 3 (Intermediate)
|
| 237 |
+
def template_gauss_law_symmetric():
|
| 238 |
+
"""
|
| 239 |
+
Gauss's Law for Symmetric Charge Distributions (Intermediate)
|
| 240 |
+
|
| 241 |
+
Scenario:
|
| 242 |
+
This template tests the application of Gauss's Law to find the electric
|
| 243 |
+
field (E) and electric flux density (D) for a highly symmetric charge
|
| 244 |
+
distribution (an infinite line or an infinite sheet). The solution emphasizes
|
| 245 |
+
the conceptual steps: choosing an appropriate Gaussian surface, applying
|
| 246 |
+
Gauss's Law to find D, and then finding E.
|
| 247 |
+
|
| 248 |
+
Core Equations:
|
| 249 |
+
Gauss's Law: Integral(D . dS) = Q_enc
|
| 250 |
+
For an infinite line: D = rho_l / (2 * pi * r)
|
| 251 |
+
For an infinite sheet: D = rho_s / 2
|
| 252 |
+
Relation: D = epsilon * E
|
| 253 |
+
|
| 254 |
+
Returns:
|
| 255 |
+
tuple: A tuple containing:
|
| 256 |
+
- str: A question about the E and D fields from a charge distribution.
|
| 257 |
+
- str: A detailed, step-by-step solution explaining the derivation.
|
| 258 |
+
"""
|
| 259 |
+
# 1. Parameterize the inputs
|
| 260 |
+
dist_type = random.choice(['line', 'sheet'])
|
| 261 |
+
epsilon_r = round(random.uniform(1.0, 6.0), 2)
|
| 262 |
+
epsilon = epsilon_r * EPSILON_0
|
| 263 |
+
precision = 3
|
| 264 |
+
|
| 265 |
+
# Initialize variables to be populated in the if/else block
|
| 266 |
+
question = ""
|
| 267 |
+
solution = ""
|
| 268 |
+
|
| 269 |
+
# --- Logic for an Infinite Line Charge ---
|
| 270 |
+
if dist_type == 'line':
|
| 271 |
+
rho_l_val = random.choice([i for i in range(-50, 51) if i != 0])
|
| 272 |
+
rho_l_calc = rho_l_val * 1e-9 # Convert nC/m to C/m
|
| 273 |
+
|
| 274 |
+
r_dist = round(random.uniform(0.1, 2.5), 2)
|
| 275 |
+
point = np.array([r_dist, 0, 0])
|
| 276 |
+
|
| 277 |
+
# Core Calculation
|
| 278 |
+
D_mag = rho_l_calc / (2 * math.pi * r_dist)
|
| 279 |
+
D_vec = D_mag * np.array([1, 0, 0]) # Direction is radial (a_r)
|
| 280 |
+
E_vec = D_vec / epsilon
|
| 281 |
+
|
| 282 |
+
# Generate Question and Solution Strings
|
| 283 |
+
question = (
|
| 284 |
+
f"An infinite line of charge with a uniform density rho_l = {rho_l_val} nC/m is located "
|
| 285 |
+
f"on the z-axis in a medium with relative permittivity epsilon_r = {epsilon_r}.\n\n"
|
| 286 |
+
f"Using Gauss's Law, find the electric flux density vector (D) and the electric field "
|
| 287 |
+
f"intensity vector (E) at the point P = {point} m."
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
solution = (
|
| 291 |
+
f"**Given:**\n"
|
| 292 |
+
f" - Infinite line charge with rho_l = {rho_l_val} nC/m = {rho_l_calc:.2e} C/m.\n"
|
| 293 |
+
f" - Relative permittivity epsilon_r = {epsilon_r}.\n"
|
| 294 |
+
f" - Observation point P = {point} m.\n\n"
|
| 295 |
+
|
| 296 |
+
f"**Step 1:** Choose a Gaussian Surface.\n"
|
| 297 |
+
f" For an infinite line charge, the electric field is purely radial. We choose a closed "
|
| 298 |
+
f"cylindrical surface of radius r = {r_dist} m and length L, coaxial with the z-axis.\n\n"
|
| 299 |
+
|
| 300 |
+
f"**Step 2:** Apply Gauss's Law.\n"
|
| 301 |
+
f" Gauss's Law states: Integral(D . dS) = Q_enc.\n"
|
| 302 |
+
f" - The flux D is perpendicular to the side surface and parallel to the top/bottom caps. "
|
| 303 |
+
f"Therefore, flux only passes through the curved side surface.\n"
|
| 304 |
+
f" - Integral(D . dS) = D_r * (Area of side) = D_r * (2 * pi * r * L).\n"
|
| 305 |
+
f" - The charge enclosed is Q_enc = rho_l * L.\n"
|
| 306 |
+
f" - Equating them: D_r * (2 * pi * r * L) = rho_l * L.\n"
|
| 307 |
+
f" - Solving for D_r: D_r = rho_l / (2 * pi * r).\n\n"
|
| 308 |
+
|
| 309 |
+
f"**Step 3:** Calculate the Electric Flux Density (D).\n"
|
| 310 |
+
f" The magnitude of D at r = {r_dist} m is:\n"
|
| 311 |
+
f" |D| = ({rho_l_calc:.2e}) / (2 * pi * {r_dist}) = {abs(D_mag):.{precision}e} C/m^2.\n"
|
| 312 |
+
f" At point P = {point} m, the direction is radial, which corresponds to the x-direction (a_x).\n"
|
| 313 |
+
f" Therefore, D = <{D_vec[0]:.{precision}e}, 0, 0> C/m^2.\n\n"
|
| 314 |
+
|
| 315 |
+
f"**Step 4:** Calculate the Electric Field Intensity (E).\n"
|
| 316 |
+
f" E = D / epsilon, where epsilon = epsilon_r * epsilon_0.\n"
|
| 317 |
+
f" epsilon = {epsilon_r} * {EPSILON_0:.3e} = {epsilon:.3e} F/m.\n"
|
| 318 |
+
f" E = <{D_vec[0]:.{precision}e}, 0, 0> / {epsilon:.3e}\n"
|
| 319 |
+
f" E = <{E_vec[0]:.{precision}e}, 0, 0> V/m.\n\n"
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
# --- Logic for an Infinite Sheet of Charge ---
|
| 323 |
+
else: # dist_type == 'sheet'
|
| 324 |
+
rho_s_val = random.choice([i for i in range(-50, 51) if i != 0])
|
| 325 |
+
rho_s_calc = rho_s_val * 1e-9 # Convert nC/m^2 to C/m^2
|
| 326 |
+
|
| 327 |
+
z_dist = round(random.uniform(0.1, 2.5), 2)
|
| 328 |
+
point = np.array([0, 0, z_dist])
|
| 329 |
+
|
| 330 |
+
# Core Calculation
|
| 331 |
+
D_mag = rho_s_calc / 2.0
|
| 332 |
+
D_vec = D_mag * np.array([0, 0, 1]) # Direction is normal (a_z)
|
| 333 |
+
E_vec = D_vec / epsilon
|
| 334 |
+
|
| 335 |
+
# Generate Question and Solution Strings
|
| 336 |
+
question = (
|
| 337 |
+
f"An infinite sheet of charge with a uniform surface density rho_s = {rho_s_val} nC/m^2 is "
|
| 338 |
+
f"located on the x-y plane (z=0) in a medium with relative permittivity epsilon_r = {epsilon_r}.\n\n"
|
| 339 |
+
f"Using Gauss's Law, find the electric flux density vector (D) and the electric field "
|
| 340 |
+
f"intensity vector (E) at the point P = {point} m."
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
solution = (
|
| 344 |
+
f"**Given:**\n"
|
| 345 |
+
f" - Infinite sheet charge with rho_s = {rho_s_val} nC/m^2 = {rho_s_calc:.2e} C/m^2.\n"
|
| 346 |
+
f" - Relative permittivity epsilon_r = {epsilon_r}.\n"
|
| 347 |
+
f" - Observation point P = {point} m.\n\n"
|
| 348 |
+
|
| 349 |
+
f"**Step 1:** Choose a Gaussian Surface.\n"
|
| 350 |
+
f" For an infinite sheet, the electric field is purely normal to the sheet. We choose a "
|
| 351 |
+
f"small cylindrical 'pillbox' or a rectangular box of area A, piercing the sheet and centered at z=0.\n\n"
|
| 352 |
+
|
| 353 |
+
f"**Step 2:** Apply Gauss's Law.\n"
|
| 354 |
+
f" Gauss's Law states: Integral(D . dS) = Q_enc.\n"
|
| 355 |
+
f" - The flux D is parallel to the sides of the pillbox, so flux only passes through the top and bottom surfaces.\n"
|
| 356 |
+
f" - Integral(D . dS) = D_z * (Top Area) + D_z * (Bottom Area) = D_z*A + D_z*A = 2*D_z*A.\n"
|
| 357 |
+
f" - The charge enclosed is Q_enc = rho_s * A.\n"
|
| 358 |
+
f" - Equating them: 2 * D_z * A = rho_s * A.\n"
|
| 359 |
+
f" - Solving for D_z: D_z = rho_s / 2.\n\n"
|
| 360 |
+
|
| 361 |
+
f"**Step 3:** Calculate the Electric Flux Density (D).\n"
|
| 362 |
+
f" The magnitude of D is independent of the distance from the sheet:\n"
|
| 363 |
+
f" |D| = |{rho_s_calc:.2e}| / 2 = {abs(D_mag):.{precision}e} C/m^2.\n"
|
| 364 |
+
f" At P = {point} m (where z > 0), the direction is normal to the sheet (a_z).\n"
|
| 365 |
+
f" Therefore, D = <0, 0, {D_vec[2]:.{precision}e}> C/m^2.\n\n"
|
| 366 |
+
|
| 367 |
+
f"**Step 4:** Calculate the Electric Field Intensity (E).\n"
|
| 368 |
+
f" E = D / epsilon, where epsilon = epsilon_r * epsilon_0.\n"
|
| 369 |
+
f" epsilon = {epsilon_r} * {EPSILON_0:.3e} = {epsilon:.3e} F/m.\n"
|
| 370 |
+
f" E = <0, 0, {D_vec[2]:.{precision}e}> / {epsilon:.3e}\n"
|
| 371 |
+
f" E = <0, 0, {E_vec[2]:.{precision}e}> V/m.\n\n"
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
# Final Summary for both cases
|
| 375 |
+
final_answer = (
|
| 376 |
+
f"**Answer:**\n"
|
| 377 |
+
f" The electric flux density is D = <{D_vec[0]:.{precision}e}, {D_vec[1]:.{precision}e}, {D_vec[2]:.{precision}e}> C/m^2.\n"
|
| 378 |
+
f" The electric field intensity is E = <{E_vec[0]:.{precision}e}, {E_vec[1]:.{precision}e}, {E_vec[2]:.{precision}e}> V/m."
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
solution += final_answer
|
| 382 |
+
|
| 383 |
+
return question, solution
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
# Template 4 (Advanced)
|
| 387 |
+
def template_coaxial_capacitance():
|
| 388 |
+
"""
|
| 389 |
+
Capacitance of a Coaxial Cable (Advanced)
|
| 390 |
+
|
| 391 |
+
Scenario:
|
| 392 |
+
This template tests the ability to derive and calculate the capacitance
|
| 393 |
+
per unit length of a coaxial cable. The solution requires a logical,
|
| 394 |
+
multi-step derivation: first, finding the electric field using the
|
| 395 |
+
premise of Gauss's Law; second, integrating the electric field to find
|
| 396 |
+
the potential difference between the conductors; and finally, using the
|
| 397 |
+
fundamental definition of capacitance (C = Q/V).
|
| 398 |
+
|
| 399 |
+
Core Equations:
|
| 400 |
+
1. E = rho_l / (2 * pi * epsilon * r)
|
| 401 |
+
2. V_ab = Integral(E . dl) = (rho_l / (2 * pi * epsilon)) * ln(b/a)
|
| 402 |
+
3. C' = rho_l / V_ab = (2 * pi * epsilon) / ln(b/a)
|
| 403 |
+
|
| 404 |
+
Returns:
|
| 405 |
+
tuple: A tuple containing:
|
| 406 |
+
- str: A question asking to derive and calculate the capacitance per unit length.
|
| 407 |
+
- str: A step-by-step solution showing the full derivation and calculation.
|
| 408 |
+
"""
|
| 409 |
+
# 1. Parameterize the inputs
|
| 410 |
+
|
| 411 |
+
# Inner radius in mm, ensuring it's not too small
|
| 412 |
+
a_mm = round(random.uniform(0.5, 2.5), 2)
|
| 413 |
+
|
| 414 |
+
# Outer radius is 2 to 5 times larger than inner radius
|
| 415 |
+
b_mm = round(a_mm * random.uniform(2, 5), 2)
|
| 416 |
+
|
| 417 |
+
# Convert radii to meters for calculation
|
| 418 |
+
a_m = a_mm * 1e-3
|
| 419 |
+
b_m = b_mm * 1e-3
|
| 420 |
+
|
| 421 |
+
# Relative permittivity of a common dielectric like polyethylene or teflon
|
| 422 |
+
epsilon_r = round(random.uniform(2.0, 4.0), 2)
|
| 423 |
+
|
| 424 |
+
# Standardize precision for final outputs
|
| 425 |
+
precision = 3
|
| 426 |
+
|
| 427 |
+
# 2. Perform the core calculation
|
| 428 |
+
|
| 429 |
+
# Total permittivity of the dielectric
|
| 430 |
+
epsilon = epsilon_r * EPSILON_0
|
| 431 |
+
|
| 432 |
+
# Capacitance per unit length in F/m
|
| 433 |
+
C_prime = (2 * math.pi * epsilon) / math.log(b_m / a_m)
|
| 434 |
+
|
| 435 |
+
# Convert to a more readable unit, pF/m
|
| 436 |
+
C_prime_pF = C_prime * 1e12
|
| 437 |
+
|
| 438 |
+
# 3. Generate the question and solution strings
|
| 439 |
+
|
| 440 |
+
question = (
|
| 441 |
+
f"A coaxial cable consists of an inner conductor of radius a = {a_mm} mm and an "
|
| 442 |
+
f"outer conductor of radius b = {b_mm} mm.\n"
|
| 443 |
+
f"The space between the conductors is filled with a dielectric material with a "
|
| 444 |
+
f"relative permittivity of epsilon_r = {epsilon_r}.\n\n"
|
| 445 |
+
f"Derive the general expression for the capacitance per unit length (C') and then "
|
| 446 |
+
f"calculate its numerical value for this cable."
|
| 447 |
+
)
|
| 448 |
+
|
| 449 |
+
solution = (
|
| 450 |
+
f"**Given:**\n"
|
| 451 |
+
f" - Inner radius, a = {a_mm} mm = {a_m:.2e} m\n"
|
| 452 |
+
f" - Outer radius, b = {b_mm} mm = {b_m:.2e} m\n"
|
| 453 |
+
f" - Relative permittivity, epsilon_r = {epsilon_r}\n\n"
|
| 454 |
+
|
| 455 |
+
f"**Derivation:**\n"
|
| 456 |
+
f"The derivation involves three main steps: finding the electric field E, finding the "
|
| 457 |
+
f"potential difference V, and then finding the capacitance C' = rho_l / V.\n\n"
|
| 458 |
+
|
| 459 |
+
f"**Step 1:** Find the Electric Field (E) between the conductors.\n"
|
| 460 |
+
f" Assume a line charge of +rho_l (C/m) on the inner conductor and -rho_l on the outer. "
|
| 461 |
+
f"By applying Gauss's Law to a cylindrical surface with radius 'r' (where a < r < b), "
|
| 462 |
+
f"the electric field is found to be purely radial:\n"
|
| 463 |
+
f" E = (rho_l / (2 * pi * epsilon * r)) * a_r\n"
|
| 464 |
+
f" where epsilon = epsilon_r * epsilon_0.\n\n"
|
| 465 |
+
|
| 466 |
+
f"**Step 2:** Find the Potential Difference (V_ab) between the conductors.\n"
|
| 467 |
+
f" The potential difference is the work done to move a unit charge from the outer "
|
| 468 |
+
f"conductor (at r=b) to the inner conductor (at r=a) against the E field.\n"
|
| 469 |
+
f" V_ab = -Integral(from b to a, E . dl)\n"
|
| 470 |
+
f" Since the path is radial, dl = dr * a_r. The integral becomes:\n"
|
| 471 |
+
f" V_ab = -Integral(from b to a, [rho_l / (2 * pi * epsilon * r)] dr)\n"
|
| 472 |
+
f" V_ab = -[rho_l / (2 * pi * epsilon)] * [ln(r)](from b to a)\n"
|
| 473 |
+
f" V_ab = -[rho_l / (2 * pi * epsilon)] * (ln(a) - ln(b))\n"
|
| 474 |
+
f" Using log properties, ln(a) - ln(b) = -ln(b/a), this simplifies to:\n"
|
| 475 |
+
f" V_ab = (rho_l / (2 * pi * epsilon)) * ln(b/a)\n\n"
|
| 476 |
+
|
| 477 |
+
f"**Step 3:** Derive the Capacitance per Unit Length (C').\n"
|
| 478 |
+
f" Capacitance per unit length is defined as the charge per unit length (rho_l) "
|
| 479 |
+
f"divided by the potential difference (V_ab).\n"
|
| 480 |
+
f" C' = rho_l / V_ab\n"
|
| 481 |
+
f" C' = rho_l / [(rho_l / (2 * pi * epsilon)) * ln(b/a)]\n"
|
| 482 |
+
f" The rho_l terms cancel, leaving the final expression:\n"
|
| 483 |
+
f" C' = (2 * pi * epsilon) / ln(b/a)\n\n"
|
| 484 |
+
|
| 485 |
+
f"**Step 4:** Calculate the final value.\n"
|
| 486 |
+
f" First, find the permittivity of the dielectric:\n"
|
| 487 |
+
f" epsilon = {epsilon_r} * {EPSILON_0:.4e} = {epsilon:.4e} F/m.\n"
|
| 488 |
+
f" Now, plug the values into the derived formula:\n"
|
| 489 |
+
f" C' = (2 * pi * {epsilon:.4e}) / ln({b_mm} / {a_mm})\n"
|
| 490 |
+
f" C' = (2 * pi * {epsilon:.4e}) / ln({b_m/a_m:.{precision}f})\n"
|
| 491 |
+
f" C' = (2 * pi * {epsilon:.4e}) / {math.log(b_m/a_m):.{precision}f}\n"
|
| 492 |
+
f" C' = {C_prime:.{precision}e} F/m.\n\n"
|
| 493 |
+
|
| 494 |
+
f"**Answer:**\n"
|
| 495 |
+
f" The derived expression for capacitance per unit length is C' = (2 * pi * epsilon) / ln(b/a).\n"
|
| 496 |
+
f" The numerical value is {C_prime:.{precision}e} F/m, which is equivalent to "
|
| 497 |
+
f"**{C_prime_pF:.{precision-1}f} pF/m**."
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
+
return question, solution
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
def main():
|
| 504 |
+
"""
|
| 505 |
+
Generate numerous instances of each electrostatics template
|
| 506 |
+
with different random seeds and write the results to a JSONL file.
|
| 507 |
+
"""
|
| 508 |
+
import json
|
| 509 |
+
import os
|
| 510 |
+
|
| 511 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 512 |
+
output_file = "testset/electrical_engineering/electromagnetics_and_waves/electrostatics.jsonl"
|
| 513 |
+
|
| 514 |
+
# Create the directory if it doesn't exist
|
| 515 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 516 |
+
|
| 517 |
+
# List of template functions with their ID and level
|
| 518 |
+
templates = [
|
| 519 |
+
(template_coulombs_law, "coulombs_law_two_charges", "Easy"),
|
| 520 |
+
(template_superposition_electric_field, "superposition_electric_field", "Intermediate"),
|
| 521 |
+
(template_gauss_law_symmetric, "gauss_law_symmetric_charge", "Intermediate"),
|
| 522 |
+
(template_coaxial_capacitance, "coaxial_cable_capacitance", "Advanced"),
|
| 523 |
+
]
|
| 524 |
+
|
| 525 |
+
# List to store all generated problems
|
| 526 |
+
all_problems = []
|
| 527 |
+
|
| 528 |
+
# Generate problems for each template
|
| 529 |
+
for template_func, id_name, level in templates:
|
| 530 |
+
for _ in range(50):
|
| 531 |
+
# Generate a unique seed for each problem
|
| 532 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 533 |
+
random.seed(seed)
|
| 534 |
+
|
| 535 |
+
# Generate the problem and solution
|
| 536 |
+
question, solution = template_func()
|
| 537 |
+
|
| 538 |
+
# Create a JSON entry
|
| 539 |
+
problem_entry = {
|
| 540 |
+
"seed": seed,
|
| 541 |
+
"branch": "electrical_engineering",
|
| 542 |
+
"domain": "electromagnetics_and_waves",
|
| 543 |
+
"area": "electrostatics",
|
| 544 |
+
"id": id_name,
|
| 545 |
+
"level": level,
|
| 546 |
+
"question": question,
|
| 547 |
+
"solution": solution
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
# Add to the list of problems
|
| 551 |
+
all_problems.append(problem_entry)
|
| 552 |
+
|
| 553 |
+
# Shuffle the problems to mix templates and levels
|
| 554 |
+
random.shuffle(all_problems)
|
| 555 |
+
|
| 556 |
+
# Write all problems to a .jsonl file
|
| 557 |
+
with open(output_file, "w") as file:
|
| 558 |
+
for problem in all_problems:
|
| 559 |
+
file.write(json.dumps(problem))
|
| 560 |
+
file.write("\n")
|
| 561 |
+
|
| 562 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
if __name__ == "__main__":
|
| 566 |
+
main()
|
data/templates/branches/electrical_engineering/electromagnetics_and_waves/magnetostatics.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_lorentz_force():
|
| 7 |
+
"""
|
| 8 |
+
Lorentz Force on a Moving Charge
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template tests the fundamental calculation of the magnetic force on a moving
|
| 12 |
+
point charge in a uniform magnetic field. It requires unit conversion and the
|
| 13 |
+
correct application of the vector cross product, which is a core skill in
|
| 14 |
+
electromagnetics.
|
| 15 |
+
|
| 16 |
+
Core Equation:
|
| 17 |
+
F_m = q * (u x B)
|
| 18 |
+
|
| 19 |
+
Returns:
|
| 20 |
+
tuple: A tuple containing:
|
| 21 |
+
- str: A question asking for the force vector and magnitude.
|
| 22 |
+
- str: A step-by-step solution.
|
| 23 |
+
"""
|
| 24 |
+
# 1. Parameterize the inputs with random values
|
| 25 |
+
|
| 26 |
+
# Generate charge in microcoulombs (uC)
|
| 27 |
+
q_uC = round(random.uniform(1.0, 100.0), 2)
|
| 28 |
+
if random.choice([True, False]):
|
| 29 |
+
q_uC *= -1
|
| 30 |
+
|
| 31 |
+
# Generate velocity vector components in m/s
|
| 32 |
+
u_vec = [random.randint(-50, 50) for _ in range(3)]
|
| 33 |
+
|
| 34 |
+
# Generate magnetic field vector components in millitesla (mT)
|
| 35 |
+
B_vec_mT = [round(random.uniform(-200.0, 200.0), 2) for _ in range(3)]
|
| 36 |
+
|
| 37 |
+
# Standardize precision for final outputs
|
| 38 |
+
precision = 3
|
| 39 |
+
|
| 40 |
+
# 2. Perform the core calculation
|
| 41 |
+
|
| 42 |
+
# Convert units for calculation
|
| 43 |
+
q_C = q_uC * 1e-6
|
| 44 |
+
B_vec_T = [b * 1e-3 for b in B_vec_mT]
|
| 45 |
+
|
| 46 |
+
# Calculate the cross product: u x B
|
| 47 |
+
cross_product_x = u_vec[1] * B_vec_T[2] - u_vec[2] * B_vec_T[1]
|
| 48 |
+
cross_product_y = u_vec[2] * B_vec_T[0] - u_vec[0] * B_vec_T[2]
|
| 49 |
+
cross_product_z = u_vec[0] * B_vec_T[1] - u_vec[1] * B_vec_T[0]
|
| 50 |
+
|
| 51 |
+
# Calculate the force vector: F = q * (u x B)
|
| 52 |
+
force_x = q_C * cross_product_x
|
| 53 |
+
force_y = q_C * cross_product_y
|
| 54 |
+
force_z = q_C * cross_product_z
|
| 55 |
+
|
| 56 |
+
# Calculate the magnitude of the force
|
| 57 |
+
force_magnitude = math.sqrt(force_x**2 + force_y**2 + force_z**2)
|
| 58 |
+
|
| 59 |
+
# 3. Generate the question and solution strings
|
| 60 |
+
|
| 61 |
+
# Helper function to format vectors for display
|
| 62 |
+
def format_vector(v, units=""):
|
| 63 |
+
return f"({v[0]} x_hat + {v[1]} y_hat + {v[2]} z_hat) {units}".strip()
|
| 64 |
+
|
| 65 |
+
question = (
|
| 66 |
+
f"A point charge of {q_uC} uC has a velocity of u = {format_vector(u_vec, 'm/s')} "
|
| 67 |
+
f"in a uniform magnetic field described by B = {format_vector(B_vec_mT, 'mT')}.\n\n"
|
| 68 |
+
f"Determine the magnetic force vector F_m acting on the charge and its magnitude."
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
solution = (
|
| 72 |
+
f"**Given:**\n"
|
| 73 |
+
f" - Charge (q): {q_uC} uC\n"
|
| 74 |
+
f" - Velocity (u): {format_vector(u_vec, 'm/s')}\n"
|
| 75 |
+
f" - Magnetic Field (B): {format_vector(B_vec_mT, 'mT')}\n\n"
|
| 76 |
+
|
| 77 |
+
f"**Step 1:** Convert Units to SI\n"
|
| 78 |
+
f" First, we convert the given values to standard SI units for the calculation.\n"
|
| 79 |
+
f" - Charge in Coulombs: q = {q_uC} * 1e-6 = {q_C:.2e} C\n"
|
| 80 |
+
f" - Magnetic Field in Tesla: B = ({B_vec_T[0]:.2e} x_hat + {B_vec_T[1]:.2e} y_hat + {B_vec_T[2]:.2e} z_hat) T\n\n"
|
| 81 |
+
|
| 82 |
+
f"**Step 2:** Calculate the Cross Product (u x B)\n"
|
| 83 |
+
f" The force is determined by the formula F_m = q * (u x B). We start by calculating the cross product.\n"
|
| 84 |
+
f" u x B = [ (u_y * B_z - u_z * B_y) x_hat + (u_z * B_x - u_x * B_z) y_hat + (u_x * B_y - u_y * B_x) z_hat ]\n"
|
| 85 |
+
f" (u x B)_x = ({u_vec[1]}) * ({B_vec_T[2]:.2e}) - ({u_vec[2]}) * ({B_vec_T[1]:.2e}) = {cross_product_x:.4f}\n"
|
| 86 |
+
f" (u x B)_y = ({u_vec[2]}) * ({B_vec_T[0]:.2e}) - ({u_vec[0]}) * ({B_vec_T[2]:.2e}) = {cross_product_y:.4f}\n"
|
| 87 |
+
f" (u x B)_z = ({u_vec[0]}) * ({B_vec_T[1]:.2e}) - ({u_vec[1]}) * ({B_vec_T[0]:.2e}) = {cross_product_z:.4f}\n"
|
| 88 |
+
f" So, u x B = ({round(cross_product_x, precision)} x_hat + {round(cross_product_y, precision)} y_hat + {round(cross_product_z, precision)} z_hat) T*m/s\n\n"
|
| 89 |
+
|
| 90 |
+
f"**Step 3:** Calculate the Force Vector (F_m)\n"
|
| 91 |
+
f" Now, multiply the cross product by the charge q.\n"
|
| 92 |
+
f" F_m = ({q_C:.2e} C) * ({round(cross_product_x, precision)} x_hat + {round(cross_product_y, precision)} y_hat + {round(cross_product_z, precision)} z_hat)\n"
|
| 93 |
+
f" F_m = ({force_x:.{precision}e} x_hat + {force_y:.{precision}e} y_hat + {force_z:.{precision}e} z_hat) N\n\n"
|
| 94 |
+
|
| 95 |
+
f"**Step 4:** Calculate the Magnitude of the Force\n"
|
| 96 |
+
f" The magnitude is the square root of the sum of the squares of the components.\n"
|
| 97 |
+
f" |F_m| = sqrt( ({force_x:.2e})^2 + ({force_y:.2e})^2 + ({force_z:.2e})^2 )\n"
|
| 98 |
+
f" |F_m| = {force_magnitude:.{precision}e} N\n\n"
|
| 99 |
+
|
| 100 |
+
f"**Answer:**\n"
|
| 101 |
+
f" The magnetic force vector is F_m = ({force_x:.{precision}e} x_hat + {force_y:.{precision}e} y_hat + {force_z:.{precision}e} z_hat) N.\n"
|
| 102 |
+
f" The magnitude of the force is |F_m| = {force_magnitude:.{precision}e} N."
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
return question, solution
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def main():
|
| 109 |
+
"""
|
| 110 |
+
Generate numerous instances of each magnetostatics template
|
| 111 |
+
with different random seeds and write the results to a JSONL file.
|
| 112 |
+
"""
|
| 113 |
+
import json
|
| 114 |
+
import os
|
| 115 |
+
|
| 116 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 117 |
+
output_file = "testset/electrical_engineering/electromagnetics_and_waves/magnetostatics.jsonl"
|
| 118 |
+
|
| 119 |
+
# Create the directory if it doesn't exist
|
| 120 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 121 |
+
|
| 122 |
+
# List of template functions with their ID and level
|
| 123 |
+
templates = [
|
| 124 |
+
(template_lorentz_force, "lorentz_force", "Easy"),
|
| 125 |
+
]
|
| 126 |
+
|
| 127 |
+
# List to store all generated problems
|
| 128 |
+
all_problems = []
|
| 129 |
+
|
| 130 |
+
# Generate problems for each template
|
| 131 |
+
for template_func, id_name, level in templates:
|
| 132 |
+
for _ in range(50):
|
| 133 |
+
# Generate a unique seed for each problem
|
| 134 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 135 |
+
random.seed(seed)
|
| 136 |
+
|
| 137 |
+
# Generate the problem and solution
|
| 138 |
+
question, solution = template_func()
|
| 139 |
+
|
| 140 |
+
# Create a JSON entry
|
| 141 |
+
problem_entry = {
|
| 142 |
+
"seed": seed,
|
| 143 |
+
"branch": "electrical_engineering",
|
| 144 |
+
"domain": "electromagnetics_and_waves",
|
| 145 |
+
"area": "magnetostatics",
|
| 146 |
+
"id": id_name,
|
| 147 |
+
"level": level,
|
| 148 |
+
"question": question,
|
| 149 |
+
"solution": solution
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
# Add to the list of problems
|
| 153 |
+
all_problems.append(problem_entry)
|
| 154 |
+
|
| 155 |
+
# Shuffle the problems to mix templates and levels
|
| 156 |
+
random.shuffle(all_problems)
|
| 157 |
+
|
| 158 |
+
# Write all problems to a .jsonl file
|
| 159 |
+
with open(output_file, "w") as file:
|
| 160 |
+
for problem in all_problems:
|
| 161 |
+
file.write(json.dumps(problem))
|
| 162 |
+
file.write("\n")
|
| 163 |
+
|
| 164 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
if __name__ == "__main__":
|
| 168 |
+
main()
|
data/templates/branches/electrical_engineering/electromagnetics_and_waves/waves_and_phasors.py
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
from data.templates.branches.electrical_engineering.constants import C0, MEDIA_VELOCITIES
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# Template 1 (Easy)
|
| 7 |
+
def template_wave_parameters_basic():
|
| 8 |
+
"""
|
| 9 |
+
Wave Parameter Fundamentals Calculation
|
| 10 |
+
|
| 11 |
+
Scenario:
|
| 12 |
+
This template explores the fundamental relationships that define a sinusoidal
|
| 13 |
+
traveling wave. These parameters (frequency, wavelength, period, etc.) describe
|
| 14 |
+
a wave's oscillatory behavior in both time and space. The problem provides the
|
| 15 |
+
wave's propagation speed (defined by its medium) and one other key parameter
|
| 16 |
+
(either frequency or wavelength), requiring the calculation of all other
|
| 17 |
+
related properties.
|
| 18 |
+
|
| 19 |
+
Core Equations:
|
| 20 |
+
u_p = f * lambda
|
| 21 |
+
omega = 2 * pi * f
|
| 22 |
+
T = 1 / f
|
| 23 |
+
k = 2 * pi / lambda
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
tuple: A tuple containing:
|
| 27 |
+
- str: A question asking to compute various wave parameters.
|
| 28 |
+
- str: A step-by-step solution showing the calculations.
|
| 29 |
+
"""
|
| 30 |
+
# 1. Parameterize the inputs with random values
|
| 31 |
+
medium_name, u_p = random.choice(list(MEDIA_VELOCITIES.items()))
|
| 32 |
+
start_with_frequency = random.choice([True, False])
|
| 33 |
+
|
| 34 |
+
if start_with_frequency:
|
| 35 |
+
# Scenario 1: Given frequency and medium
|
| 36 |
+
f_mhz = round(random.uniform(50, 500), 1)
|
| 37 |
+
f = f_mhz * 1e6
|
| 38 |
+
|
| 39 |
+
# 2. Perform the core calculations
|
| 40 |
+
omega = 2 * math.pi * f
|
| 41 |
+
T = 1 / f
|
| 42 |
+
lambda_ = u_p / f
|
| 43 |
+
k = 2 * math.pi / lambda_
|
| 44 |
+
|
| 45 |
+
# 3. Generate the question and solution strings (Plain Text)
|
| 46 |
+
question = (
|
| 47 |
+
f"A sinusoidal wave with a frequency of {f_mhz} MHz is propagating "
|
| 48 |
+
f"through {medium_name}. Given that the phase velocity in this medium is "
|
| 49 |
+
f"{u_p:.2e} m/s, calculate the wave's:\n"
|
| 50 |
+
f"a) Angular frequency (omega)\n"
|
| 51 |
+
f"b) Period (T)\n"
|
| 52 |
+
f"c) Wavelength (lambda)\n"
|
| 53 |
+
f"d) Wavenumber (k)"
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
solution = (
|
| 57 |
+
f"**Given:**\n"
|
| 58 |
+
f"- Frequency (f) = {f_mhz} MHz = {f:.2e} Hz\n"
|
| 59 |
+
f"- Medium = {medium_name}\n"
|
| 60 |
+
f"- Phase Velocity (u_p) = {u_p:.2e} m/s\n\n"
|
| 61 |
+
|
| 62 |
+
f"**Step 1:** Calculate the Angular Frequency (omega)\n"
|
| 63 |
+
f"The angular frequency is related to frequency by omega = 2 * pi * f.\n"
|
| 64 |
+
f" omega = 2 * {math.pi:.5f} * ({f:.2e} Hz) = {omega:.2e} rad/s\n\n"
|
| 65 |
+
|
| 66 |
+
f"**Step 2:** Calculate the Period (T)\n"
|
| 67 |
+
f"The period is the inverse of the frequency, T = 1 / f.\n"
|
| 68 |
+
f" T = 1 / ({f:.2e} Hz) = {T:.2e} s\n\n"
|
| 69 |
+
|
| 70 |
+
f"**Step 3:** Calculate the Wavelength (lambda)\n"
|
| 71 |
+
f"The wavelength is found using the phase velocity, lambda = u_p / f.\n"
|
| 72 |
+
f" lambda = ({u_p:.2e} m/s) / ({f:.2e} Hz) = {round(lambda_, 3)} m\n\n"
|
| 73 |
+
|
| 74 |
+
f"**Step 4:** Calculate the Wavenumber (k)\n"
|
| 75 |
+
f"The wavenumber (or phase constant) is given by k = 2 * pi / lambda.\n"
|
| 76 |
+
f" k = 2 * {math.pi:.5f} / {round(lambda_, 3)} m = {round(k, 2)} rad/m\n\n"
|
| 77 |
+
|
| 78 |
+
f"**Answer:**\n"
|
| 79 |
+
f"- Angular Frequency (omega): {omega:.2e} rad/s\n"
|
| 80 |
+
f"- Period (T): {T:.2e} s\n"
|
| 81 |
+
f"- Wavelength (lambda): {round(lambda_, 3)} m\n"
|
| 82 |
+
f"- Wavenumber (k): {round(k, 2)} rad/m"
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
else:
|
| 86 |
+
# Scenario 2: Given wavelength and medium
|
| 87 |
+
lambda_ = round(random.uniform(0.1, 2.0), 2)
|
| 88 |
+
|
| 89 |
+
# 2. Perform the core calculations
|
| 90 |
+
f = u_p / lambda_
|
| 91 |
+
omega = 2 * math.pi * f
|
| 92 |
+
T = 1 / f
|
| 93 |
+
k = 2 * math.pi / lambda_
|
| 94 |
+
|
| 95 |
+
# 3. Generate the question and solution strings (Plain Text)
|
| 96 |
+
question = (
|
| 97 |
+
f"An electromagnetic wave traveling in {medium_name} is observed to have "
|
| 98 |
+
f"a wavelength of {lambda_} m. Given that the phase velocity in this "
|
| 99 |
+
f"medium is {u_p:.2e} m/s, determine the wave's:\n"
|
| 100 |
+
f"a) Frequency (f)\n"
|
| 101 |
+
f"b) Angular frequency (omega)\n"
|
| 102 |
+
f"c) Period (T)\n"
|
| 103 |
+
f"d) Wavenumber (k)"
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
solution = (
|
| 107 |
+
f"**Given:**\n"
|
| 108 |
+
f"- Wavelength (lambda) = {lambda_} m\n"
|
| 109 |
+
f"- Medium = {medium_name}\n"
|
| 110 |
+
f"- Phase Velocity (u_p) = {u_p:.2e} m/s\n\n"
|
| 111 |
+
|
| 112 |
+
f"**Step 1:** Calculate the Frequency (f)\n"
|
| 113 |
+
f"Frequency is found using the relation u_p = f * lambda, so f = u_p / lambda.\n"
|
| 114 |
+
f" f = ({u_p:.2e} m/s) / ({lambda_} m) = {f:.2e} Hz = {f/1e6:.2f} MHz\n\n"
|
| 115 |
+
|
| 116 |
+
f"**Step 2:** Calculate the Angular Frequency (omega)\n"
|
| 117 |
+
f"The angular frequency is omega = 2 * pi * f.\n"
|
| 118 |
+
f" omega = 2 * {math.pi:.5f} * ({f:.2e} Hz) = {omega:.2e} rad/s\n\n"
|
| 119 |
+
|
| 120 |
+
f"**Step 3:** Calculate the Period (T)\n"
|
| 121 |
+
f"The period is the inverse of the frequency, T = 1 / f.\n"
|
| 122 |
+
f" T = 1 / ({f:.2e} Hz) = {T:.2e} s\n\n"
|
| 123 |
+
|
| 124 |
+
f"**Step 4:** Calculate the Wavenumber (k)\n"
|
| 125 |
+
f"The wavenumber is given by k = 2 * pi / lambda.\n"
|
| 126 |
+
f" k = 2 * {math.pi:.5f} / {lambda_} m = {round(k, 2)} rad/m\n\n"
|
| 127 |
+
|
| 128 |
+
f"**Answer:**\n"
|
| 129 |
+
f"- Frequency (f): {f/1e6:.2f} MHz\n"
|
| 130 |
+
f"- Angular Frequency (omega): {omega:.2e} rad/s\n"
|
| 131 |
+
f"- Period (T): {T:.2e} s\n"
|
| 132 |
+
f"- Wavenumber (k): {round(k, 2)} rad/m"
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
return question, solution
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
# Template 2 (Easy)
|
| 139 |
+
def template_time_to_phasor():
|
| 140 |
+
"""
|
| 141 |
+
Time-Domain to Phasor Conversion
|
| 142 |
+
|
| 143 |
+
Scenario:
|
| 144 |
+
This template tests the ability to convert a sinusoidal time-domain function
|
| 145 |
+
into its corresponding phasor representation. This is a foundational skill for
|
| 146 |
+
AC circuit analysis and wave analysis. The conversion requires identifying the
|
| 147 |
+
amplitude and phase, and applying a phase shift if the function is a sine wave.
|
| 148 |
+
|
| 149 |
+
Core Equations:
|
| 150 |
+
For v(t) = A * cos(omega*t + phi):
|
| 151 |
+
Phasor V = A * exp(j*phi) = A < phi
|
| 152 |
+
|
| 153 |
+
Identity: sin(theta) = cos(theta - 90 deg)
|
| 154 |
+
|
| 155 |
+
Returns:
|
| 156 |
+
tuple: A tuple containing:
|
| 157 |
+
- str: A question asking for the phasor form of a given time-domain signal.
|
| 158 |
+
- str: A step-by-step solution showing the conversion.
|
| 159 |
+
"""
|
| 160 |
+
# 1. Parameterize the inputs with random values
|
| 161 |
+
amplitude = round(random.uniform(5.0, 150.0), 2)
|
| 162 |
+
omega = random.randint(100, 1000)
|
| 163 |
+
func_type = random.choice(['cos', 'sin'])
|
| 164 |
+
use_degrees = random.choice([True, False])
|
| 165 |
+
|
| 166 |
+
# Standardize precision for all calculations and outputs
|
| 167 |
+
precision = 2
|
| 168 |
+
|
| 169 |
+
# Generate phase in either degrees or radians
|
| 170 |
+
if use_degrees:
|
| 171 |
+
phi_deg = round(random.uniform(-180.0, 180.0), 1)
|
| 172 |
+
phi_rad = math.radians(phi_deg)
|
| 173 |
+
phi_str = f"{phi_deg} deg"
|
| 174 |
+
else:
|
| 175 |
+
phi_rad = round(random.uniform(-math.pi, math.pi), precision)
|
| 176 |
+
phi_deg = math.degrees(phi_rad)
|
| 177 |
+
phi_str = f"{phi_rad} rad"
|
| 178 |
+
|
| 179 |
+
# Construct the time-domain function string for the question
|
| 180 |
+
time_domain_expr = f"{amplitude} * {func_type}({omega}*t + {phi_str})"
|
| 181 |
+
|
| 182 |
+
# 2. Perform the core calculation
|
| 183 |
+
|
| 184 |
+
# The final amplitude of the phasor is the amplitude of the signal
|
| 185 |
+
phasor_amplitude = amplitude
|
| 186 |
+
|
| 187 |
+
# The final phase depends on whether the function is cos or sin
|
| 188 |
+
if func_type == 'cos':
|
| 189 |
+
phasor_phi_deg = phi_deg
|
| 190 |
+
phasor_phi_rad = phi_rad
|
| 191 |
+
conversion_step = (
|
| 192 |
+
"**Step 2:** Identify the function type.\n"
|
| 193 |
+
" The function is a cosine, which is the standard reference for phasors. "
|
| 194 |
+
"No phase adjustment is needed.\n"
|
| 195 |
+
f" The initial phase is {round(phi_deg, precision)} degrees.\n"
|
| 196 |
+
)
|
| 197 |
+
else: # func_type == 'sin'
|
| 198 |
+
phasor_phi_deg = phi_deg - 90
|
| 199 |
+
phasor_phi_rad = math.radians(phasor_phi_deg)
|
| 200 |
+
conversion_step = (
|
| 201 |
+
"**Step 2:** Convert the sine function to cosine.\n"
|
| 202 |
+
" The sine function leads the cosine function by 90 degrees. To convert, "
|
| 203 |
+
"we use the identity sin(theta) = cos(theta - 90 deg).\n"
|
| 204 |
+
f" New phase = (Initial Phase) - 90 deg = {round(phi_deg, precision)} - 90 = {round(phasor_phi_deg, precision)} degrees.\n"
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
# Normalize the final phase angle to be between -180 and 180 degrees
|
| 208 |
+
phasor_phi_deg = (phasor_phi_deg + 180) % 360 - 180
|
| 209 |
+
phasor_phi_rad = math.radians(phasor_phi_deg)
|
| 210 |
+
|
| 211 |
+
# Calculate rectangular components
|
| 212 |
+
real_part = phasor_amplitude * math.cos(phasor_phi_rad)
|
| 213 |
+
imag_part = phasor_amplitude * math.sin(phasor_phi_rad)
|
| 214 |
+
|
| 215 |
+
# 3. Generate the question and solution strings
|
| 216 |
+
question = (
|
| 217 |
+
f"A signal is described by the time-domain function:\n"
|
| 218 |
+
f" v(t) = {time_domain_expr}\n\n"
|
| 219 |
+
f"Determine the phasor representation of this signal in both polar (A < phi) "
|
| 220 |
+
f"and rectangular (x + jy) forms. Use degrees for the phase angle."
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
solution = (
|
| 224 |
+
f"**Given:**\n"
|
| 225 |
+
f" The time-domain signal is v(t) = {time_domain_expr}.\n\n"
|
| 226 |
+
|
| 227 |
+
f"**Step 1:** Extract the amplitude and initial phase.\n"
|
| 228 |
+
f" By inspection, the amplitude (A) is {amplitude}.\n"
|
| 229 |
+
f" The initial phase is {phi_str}.\n\n"
|
| 230 |
+
|
| 231 |
+
f"{conversion_step}\n"
|
| 232 |
+
|
| 233 |
+
f"**Step 3:** Write the phasor in polar form.\n"
|
| 234 |
+
f" Phase angles are conventionally expressed in the range [-180°, 180°].\n"
|
| 235 |
+
f" The phasor has the signal's amplitude and the adjusted phase.\n"
|
| 236 |
+
f" Phasor V = {round(phasor_amplitude, precision)} < {round(phasor_phi_deg, precision)} degrees.\n\n"
|
| 237 |
+
|
| 238 |
+
f"**Step 4:** Convert the polar form to rectangular form (x + jy).\n"
|
| 239 |
+
f" x (real part) = A * cos(phi) = {round(phasor_amplitude, precision)} * cos({round(phasor_phi_deg, precision)} deg) = {round(real_part, precision)}\n"
|
| 240 |
+
f" y (imaginary part) = A * sin(phi) = {round(phasor_amplitude, precision)} * sin({round(phasor_phi_deg, precision)} deg) = {round(imag_part, precision)}\n"
|
| 241 |
+
f" Phasor V = {round(real_part, precision)} + j{round(imag_part, precision)}\n\n"
|
| 242 |
+
|
| 243 |
+
f"**Answer:**\n"
|
| 244 |
+
f" The phasor representation is {round(phasor_amplitude, precision)} < {round(phasor_phi_deg, precision)} degrees, "
|
| 245 |
+
f"which is equivalent to {round(real_part, precision)} + j{round(imag_part, precision)}."
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
return question, solution
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
# Template 3 (Intermediate)
|
| 252 |
+
def template_wave_equation_interpretation():
|
| 253 |
+
"""
|
| 254 |
+
Wave Equation Interpretation
|
| 255 |
+
|
| 256 |
+
Scenario:
|
| 257 |
+
This template tests the ability to extract fundamental wave parameters directly
|
| 258 |
+
from the mathematical expression of a traveling wave. It requires matching the
|
| 259 |
+
given equation to the standard form to identify key coefficients (amplitude,
|
| 260 |
+
angular frequency, wavenumber) and then using them to calculate related properties.
|
| 261 |
+
|
| 262 |
+
Core Equations:
|
| 263 |
+
f = omega / (2 * pi)
|
| 264 |
+
lambda = 2 * pi / k
|
| 265 |
+
u_p = omega / k
|
| 266 |
+
|
| 267 |
+
Returns:
|
| 268 |
+
tuple: A tuple containing:
|
| 269 |
+
- str: A question presenting a wave equation and asking for its properties.
|
| 270 |
+
- str: A step-by-step solution showing the analysis and calculations.
|
| 271 |
+
"""
|
| 272 |
+
# 1. Parameterize the inputs with random values
|
| 273 |
+
amplitude = random.randint(10, 200)
|
| 274 |
+
|
| 275 |
+
# Generate a realistic angular frequency (omega)
|
| 276 |
+
omega_multiple = random.randint(2, 9)
|
| 277 |
+
omega = omega_multiple * math.pi * 1e8
|
| 278 |
+
|
| 279 |
+
# Generate a realistic phase velocity (u_p) by choosing a refractive index
|
| 280 |
+
# This ensures omega and k are physically consistent.
|
| 281 |
+
refractive_index = round(random.uniform(1.0, 2.5), 2)
|
| 282 |
+
u_p = C0 / refractive_index
|
| 283 |
+
|
| 284 |
+
# Calculate wavenumber (k) based on omega and u_p
|
| 285 |
+
k = omega / u_p
|
| 286 |
+
|
| 287 |
+
# Randomly determine the direction of propagation and phase
|
| 288 |
+
direction_sign_str = random.choice(['+', '-'])
|
| 289 |
+
phi_deg = random.randint(-180, 180)
|
| 290 |
+
|
| 291 |
+
# Determine direction text for the solution
|
| 292 |
+
if direction_sign_str == '-':
|
| 293 |
+
direction_text = "the positive z-direction"
|
| 294 |
+
else:
|
| 295 |
+
direction_text = "the negative z-direction"
|
| 296 |
+
|
| 297 |
+
# Construct the wave equation string for the question
|
| 298 |
+
# Format omega for better readability in the question
|
| 299 |
+
omega_str = f"{omega_multiple}pi x 10^8"
|
| 300 |
+
wave_equation = (
|
| 301 |
+
f"E(z, t) = {amplitude} * cos({omega_str}*t {direction_sign_str} {round(k, 2)}*z + {phi_deg} deg) V/m"
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
# 2. Perform the core calculations for the solution
|
| 305 |
+
frequency = omega / (2 * math.pi)
|
| 306 |
+
wavelength = (2 * math.pi) / k
|
| 307 |
+
|
| 308 |
+
# 3. Generate the question and solution strings
|
| 309 |
+
question = (
|
| 310 |
+
f"An electric field wave is described by the following equation:\n"
|
| 311 |
+
f" {wave_equation}\n\n"
|
| 312 |
+
f"Based on this expression, determine the following properties of the wave:\n"
|
| 313 |
+
f"a) Amplitude (A)\n"
|
| 314 |
+
f"b) Direction of propagation\n"
|
| 315 |
+
f"c) Frequency (f)\n"
|
| 316 |
+
f"d) Wavelength (lambda)\n"
|
| 317 |
+
f"e) Phase velocity (u_p)"
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
solution = (
|
| 321 |
+
f"**Given:**\n"
|
| 322 |
+
f" The wave equation is E(z, t) = {wave_equation}.\n\n"
|
| 323 |
+
|
| 324 |
+
f"**Step 1:** Compare the equation to the standard form.\n"
|
| 325 |
+
f" The standard form for a traveling wave is A * cos(omega*t +/- k*z + phi).\n"
|
| 326 |
+
f" By matching the terms, we can extract the coefficients:\n"
|
| 327 |
+
f" - Amplitude (A) = {amplitude} V/m\n"
|
| 328 |
+
f" - Angular Frequency (omega) = {omega_str} rad/s = {omega:.3e} rad/s\n"
|
| 329 |
+
f" - Wavenumber (k) = {round(k, 2)} rad/m\n"
|
| 330 |
+
f" - The sign between the t and z terms is '{direction_sign_str}'.\n\n"
|
| 331 |
+
|
| 332 |
+
f"**Step 2:** Determine the Amplitude and Direction of Propagation.\n"
|
| 333 |
+
f" The amplitude (A) is the value multiplying the cosine function, which is {amplitude} V/m.\n"
|
| 334 |
+
f" The direction is determined by the sign in front of the 'k*z' term. A minus sign (-) indicates propagation in the positive direction, while a plus sign (+) indicates the negative direction.\n"
|
| 335 |
+
f" Therefore, the wave is traveling in {direction_text}.\n\n"
|
| 336 |
+
|
| 337 |
+
f"**Step 3:** Calculate the Frequency (f).\n"
|
| 338 |
+
f" Frequency is related to angular frequency by f = omega / (2 * pi).\n"
|
| 339 |
+
f" f = ({omega:.3e} rad/s) / (2 * pi) = {frequency:.3e} Hz = {frequency/1e6:.2f} MHz.\n\n"
|
| 340 |
+
|
| 341 |
+
f"**Step 4:** Calculate the Wavelength (lambda).\n"
|
| 342 |
+
f" Wavelength is related to the wavenumber by lambda = 2 * pi / k.\n"
|
| 343 |
+
f" lambda = (2 * pi) / ({round(k, 2)} rad/m) = {round(wavelength, 3)} m.\n\n"
|
| 344 |
+
|
| 345 |
+
f"**Step 5:** Calculate the Phase Velocity (u_p).\n"
|
| 346 |
+
f" Phase velocity is the speed of the wave, given by u_p = omega / k.\n"
|
| 347 |
+
f" u_p = ({omega:.3e} rad/s) / ({round(k, 2)} rad/m) = {u_p:.3e} m/s.\n\n"
|
| 348 |
+
|
| 349 |
+
f"**Answer:**\n"
|
| 350 |
+
f" - Amplitude: {amplitude} V/m\n"
|
| 351 |
+
f" - Direction of Propagation: {direction_text}\n"
|
| 352 |
+
f" - Frequency: {frequency/1e6:.2f} MHz\n"
|
| 353 |
+
f" - Wavelength: {round(wavelength, 3)} m\n"
|
| 354 |
+
f" - Phase Velocity: {u_p:.3e} m/s"
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
return question, solution
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
# Template 4 (Intermediate)
|
| 361 |
+
def template_phasor_addition():
|
| 362 |
+
"""
|
| 363 |
+
Phasor Addition of Sinusoidal Waves
|
| 364 |
+
|
| 365 |
+
Scenario:
|
| 366 |
+
This template assesses the ability to add two sinusoidal waves of the same
|
| 367 |
+
frequency. The standard method is to convert both signals into their phasor
|
| 368 |
+
representations, perform complex number addition in rectangular form, convert
|
| 369 |
+
the result back to polar form, and finally express it as a time-domain signal.
|
| 370 |
+
|
| 371 |
+
Core Equations:
|
| 372 |
+
V_phasor = A < phi
|
| 373 |
+
x = A * cos(phi)
|
| 374 |
+
y = A * sin(phi)
|
| 375 |
+
A_total = sqrt(x_total^2 + y_total^2)
|
| 376 |
+
phi_total = atan2(y_total, x_total)
|
| 377 |
+
v_total(t) = A_total * cos(omega*t + phi_total)
|
| 378 |
+
|
| 379 |
+
Returns:
|
| 380 |
+
tuple: A tuple containing:
|
| 381 |
+
- str: A question asking for the sum of two time-domain signals.
|
| 382 |
+
- str: A step-by-step solution using the phasor addition method.
|
| 383 |
+
"""
|
| 384 |
+
# 1. Parameterize the inputs
|
| 385 |
+
precision = 2
|
| 386 |
+
|
| 387 |
+
# Amplitudes
|
| 388 |
+
A1 = round(random.uniform(10.0, 50.0), precision)
|
| 389 |
+
A2 = round(random.uniform(10.0, 50.0), precision)
|
| 390 |
+
|
| 391 |
+
# Phases in degrees
|
| 392 |
+
phi1_deg = round(random.uniform(-180.0, 180.0), 1)
|
| 393 |
+
phi2_deg = round(random.uniform(-180.0, 180.0), 1)
|
| 394 |
+
|
| 395 |
+
# Shared angular frequency
|
| 396 |
+
omega = random.randint(100, 500)
|
| 397 |
+
|
| 398 |
+
# Function types (cos or sin)
|
| 399 |
+
func_type1 = random.choice(['cos', 'sin'])
|
| 400 |
+
func_type2 = random.choice(['cos', 'sin'])
|
| 401 |
+
|
| 402 |
+
# 2. Generate the question string
|
| 403 |
+
v1_str = f"{A1} * {func_type1}({omega}*t + {phi1_deg} deg)"
|
| 404 |
+
v2_str = f"{A2} * {func_type2}({omega}*t + {phi2_deg} deg)"
|
| 405 |
+
|
| 406 |
+
question = (
|
| 407 |
+
f"Two signals, v1(t) and v2(t), are defined as:\n"
|
| 408 |
+
f" v1(t) = {v1_str}\n"
|
| 409 |
+
f" v2(t) = {v2_str}\n\n"
|
| 410 |
+
f"Find the sum, v_total(t) = v1(t) + v2(t), using the phasor method. "
|
| 411 |
+
f"Express the final answer as a single cosine function."
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
# 3. Perform the calculations for the solution
|
| 415 |
+
|
| 416 |
+
# --- Phasor 1 Conversion ---
|
| 417 |
+
phi1_adj_deg = phi1_deg - 90 if func_type1 == 'sin' else phi1_deg
|
| 418 |
+
phi1_rad = math.radians(phi1_adj_deg)
|
| 419 |
+
x1 = A1 * math.cos(phi1_rad)
|
| 420 |
+
y1 = A1 * math.sin(phi1_rad)
|
| 421 |
+
|
| 422 |
+
# --- Phasor 2 Conversion ---
|
| 423 |
+
phi2_adj_deg = phi2_deg - 90 if func_type2 == 'sin' else phi2_deg
|
| 424 |
+
phi2_rad = math.radians(phi2_adj_deg)
|
| 425 |
+
x2 = A2 * math.cos(phi2_rad)
|
| 426 |
+
y2 = A2 * math.sin(phi2_rad)
|
| 427 |
+
|
| 428 |
+
# --- Addition ---
|
| 429 |
+
x_total = x1 + x2
|
| 430 |
+
y_total = y1 + y2
|
| 431 |
+
|
| 432 |
+
# --- Convert Sum back to Polar ---
|
| 433 |
+
A_total = math.hypot(x_total, y_total) # sqrt(x^2 + y^2)
|
| 434 |
+
phi_total_rad = math.atan2(y_total, x_total)
|
| 435 |
+
phi_total_deg = math.degrees(phi_total_rad)
|
| 436 |
+
|
| 437 |
+
# --- Final time-domain expression ---
|
| 438 |
+
v_total_str = f"{round(A_total, precision)} * cos({omega}*t + {round(phi_total_deg, precision)} deg)"
|
| 439 |
+
|
| 440 |
+
# 4. Generate the solution string
|
| 441 |
+
|
| 442 |
+
# Helper strings for conversion steps
|
| 443 |
+
conv1_step = f" Since v1(t) is a {func_type1} function, we adjust the phase: {phi1_deg} - 90 = {round(phi1_adj_deg, 1)} deg." if func_type1 == 'sin' else f" Since v1(t) is a cosine function, no phase adjustment is needed. Phase = {phi1_deg} deg."
|
| 444 |
+
conv2_step = f" Since v2(t) is a {func_type2} function, we adjust the phase: {phi2_deg} - 90 = {round(phi2_adj_deg, 1)} deg." if func_type2 == 'sin' else f" Since v2(t) is a cosine function, no phase adjustment is needed. Phase = {phi2_deg} deg."
|
| 445 |
+
|
| 446 |
+
solution = (
|
| 447 |
+
f"**Given:**\n"
|
| 448 |
+
f" v1(t) = {v1_str}\n"
|
| 449 |
+
f" v2(t) = {v2_str}\n\n"
|
| 450 |
+
|
| 451 |
+
f"**Step 1:** Convert v1(t) to its phasor form V1.\n"
|
| 452 |
+
f"{conv1_step}\n"
|
| 453 |
+
f" The polar form is V1 = {A1} < {round(phi1_adj_deg, 1)} deg.\n"
|
| 454 |
+
f" Convert to rectangular form (x1 + jy1):\n"
|
| 455 |
+
f" x1 = {A1} * cos({round(phi1_adj_deg, 1)}) = {round(x1, precision)}\n"
|
| 456 |
+
f" y1 = {A1} * sin({round(phi1_adj_deg, 1)}) = {round(y1, precision)}\n"
|
| 457 |
+
f" So, V1 = {round(x1, precision)} + j{round(y1, precision)}.\n\n"
|
| 458 |
+
|
| 459 |
+
f"**Step 2:** Convert v2(t) to its phasor form V2.\n"
|
| 460 |
+
f"{conv2_step}\n"
|
| 461 |
+
f" The polar form is V2 = {A2} < {round(phi2_adj_deg, 1)} deg.\n"
|
| 462 |
+
f" Convert to rectangular form (x2 + jy2):\n"
|
| 463 |
+
f" x2 = {A2} * cos({round(phi2_adj_deg, 1)}) = {round(x2, precision)}\n"
|
| 464 |
+
f" y2 = {A2} * sin({round(phi2_adj_deg, 1)}) = {round(y2, precision)}\n"
|
| 465 |
+
f" So, V2 = {round(x2, precision)} + j{round(y2, precision)}.\n\n"
|
| 466 |
+
|
| 467 |
+
f"**Step 3:** Add the phasors in rectangular form: V_total = V1 + V2.\n"
|
| 468 |
+
f" V_total = ({round(x1, precision)} + j{round(y1, precision)}) + ({round(x2, precision)} + j{round(y2, precision)})\n"
|
| 469 |
+
f" V_total = ({round(x1, precision)} + {round(x2, precision)}) + j({round(y1, precision)} + {round(y2, precision)})\n"
|
| 470 |
+
f" V_total = {round(x_total, precision)} + j{round(y_total, precision)}.\n\n"
|
| 471 |
+
|
| 472 |
+
f"**Step 4:** Convert the resultant phasor V_total back to polar form (A < phi).\n"
|
| 473 |
+
f" A_total = sqrt({round(x_total, precision)}^2 + {round(y_total, precision)}^2) = {round(A_total, precision)}\n"
|
| 474 |
+
f" phi_total = atan2({round(y_total, precision)}, {round(x_total, precision)}) = {round(phi_total_deg, precision)} deg\n"
|
| 475 |
+
f" So, V_total = {round(A_total, precision)} < {round(phi_total_deg, precision)} deg.\n\n"
|
| 476 |
+
|
| 477 |
+
f"**Step 5:** Convert the final phasor back to the time domain.\n"
|
| 478 |
+
f" The resulting signal has amplitude A_total, phase phi_total, and the original angular frequency omega.\n"
|
| 479 |
+
f" v_total(t) = {v_total_str}\n\n"
|
| 480 |
+
|
| 481 |
+
f"**Answer:**\n"
|
| 482 |
+
f" The sum of the two signals is v_total(t) = {v_total_str}."
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
return question, solution
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
# Template 5 (Advanced)
|
| 489 |
+
def template_standing_wave_formation():
|
| 490 |
+
"""
|
| 491 |
+
Standing Wave Formation and Properties
|
| 492 |
+
|
| 493 |
+
Scenario:
|
| 494 |
+
This template models the superposition of two identical waves traveling in
|
| 495 |
+
opposite directions, which creates a standing wave. It requires using a
|
| 496 |
+
trigonometric identity to derive the mathematical form of the standing wave
|
| 497 |
+
and then interpreting this form to find the locations of nodes (zero
|
| 498 |
+
amplitude) and antinodes (maximum amplitude).
|
| 499 |
+
|
| 500 |
+
Core Equations:
|
| 501 |
+
cos(a) + cos(b) = 2 * cos((a-b)/2) * cos((a+b)/2)
|
| 502 |
+
Standing Wave: y(x,t) = [2*A*cos(k*x)] * cos(omega*t)
|
| 503 |
+
Nodes: k*x = (n + 1/2)*pi
|
| 504 |
+
Antinodes: k*x = n*pi
|
| 505 |
+
|
| 506 |
+
Returns:
|
| 507 |
+
tuple: A tuple containing:
|
| 508 |
+
- str: A question asking to derive and analyze a standing wave.
|
| 509 |
+
- str: A step-by-step solution showing the derivation and calculations.
|
| 510 |
+
"""
|
| 511 |
+
# 1. Parameterize the inputs
|
| 512 |
+
precision = 2
|
| 513 |
+
A = random.randint(5, 50)
|
| 514 |
+
omega = random.randint(100, 500)
|
| 515 |
+
k = round(random.uniform(1.0, 5.0), precision)
|
| 516 |
+
|
| 517 |
+
# 2. Generate the question string
|
| 518 |
+
y1_str = f"{A} * cos({omega}*t - {k}*x)"
|
| 519 |
+
y2_str = f"{A} * cos({omega}*t + {k}*x)"
|
| 520 |
+
|
| 521 |
+
question = (
|
| 522 |
+
f"Two waves of the same amplitude, frequency, and wavelength travel in opposite directions, given by:\n"
|
| 523 |
+
f" y1(x,t) = {y1_str}\n"
|
| 524 |
+
f" y2(x,t) = {y2_str}\n\n"
|
| 525 |
+
f"a) Find the superposition of these waves, y(x,t) = y1(x,t) + y2(x,t), and show that it represents a standing wave.\n"
|
| 526 |
+
f"b) Determine the locations of the first three nodes and antinodes for x >= 0."
|
| 527 |
+
)
|
| 528 |
+
|
| 529 |
+
# 3. Perform the calculations for the solution
|
| 530 |
+
|
| 531 |
+
# Standing wave amplitude
|
| 532 |
+
standing_wave_amplitude = 2 * A
|
| 533 |
+
|
| 534 |
+
# Wavelength
|
| 535 |
+
wavelength = (2 * math.pi) / k
|
| 536 |
+
|
| 537 |
+
# Node locations
|
| 538 |
+
nodes = [(n + 0.5) * math.pi / k for n in range(3)]
|
| 539 |
+
|
| 540 |
+
# Antinode locations
|
| 541 |
+
antinodes = [n * math.pi / k for n in range(3)]
|
| 542 |
+
|
| 543 |
+
# 4. Generate the solution string
|
| 544 |
+
|
| 545 |
+
standing_wave_eq = f"{standing_wave_amplitude} * cos({k}*x) * cos({omega}*t)"
|
| 546 |
+
|
| 547 |
+
solution = (
|
| 548 |
+
f"**Given:**\n"
|
| 549 |
+
f" Incident wave y1(x,t) = {y1_str}\n"
|
| 550 |
+
f" Reflected wave y2(x,t) = {y2_str}\n\n"
|
| 551 |
+
|
| 552 |
+
f"**Step 1:** Sum the two traveling waves using a trigonometric identity.\n"
|
| 553 |
+
f" We use the identity: cos(alpha) + cos(beta) = 2 * cos((alpha - beta)/2) * cos((alpha + beta)/2).\n"
|
| 554 |
+
f" Let alpha = {omega}*t - {k}*x and beta = {omega}*t + {k}*x.\n"
|
| 555 |
+
f" (alpha - beta)/2 = (({omega}*t - {k}*x) - ({omega}*t + {k}*x))/2 = -{k}*x\n"
|
| 556 |
+
f" (alpha + beta)/2 = (({omega}*t - {k}*x) + ({omega}*t + {k}*x))/2 = {omega}*t\n"
|
| 557 |
+
f" Substituting these into the identity and using cos(-z) = cos(z), we get:\n"
|
| 558 |
+
f" y(x,t) = {A} * [2 * cos(-{k}*x) * cos({omega}*t)] = {standing_wave_eq}\n"
|
| 559 |
+
f" This is the equation of a standing wave because the spatial dependence (cos({k}*x)) is separate from the time dependence (cos({omega}*t)).\n\n"
|
| 560 |
+
|
| 561 |
+
f"**Step 2:** Find the locations of the nodes.\n"
|
| 562 |
+
f" Nodes are points of zero amplitude, which occur when the spatial term is zero: cos({k}*x) = 0.\n"
|
| 563 |
+
f" This condition is met when k*x = (n + 1/2)*pi, for n = 0, 1, 2, ...\n"
|
| 564 |
+
f" Solving for x: x = (n + 1/2) * pi / k.\n"
|
| 565 |
+
f" - For n=0: x = (0.5 * pi) / {k} = {round(nodes[0], precision)}\n"
|
| 566 |
+
f" - For n=1: x = (1.5 * pi) / {k} = {round(nodes[1], precision)}\n"
|
| 567 |
+
f" - For n=2: x = (2.5 * pi) / {k} = {round(nodes[2], precision)}\n\n"
|
| 568 |
+
|
| 569 |
+
f"**Step 3:** Find the locations of the antinodes.\n"
|
| 570 |
+
f" Antinodes are points of maximum amplitude, which occur when |cos({k}*x)| = 1.\n"
|
| 571 |
+
f" This condition is met when k*x = n*pi, for n = 0, 1, 2, ...\n"
|
| 572 |
+
f" Solving for x: x = n * pi / k.\n"
|
| 573 |
+
f" - For n=0: x = (0 * pi) / {k} = {round(antinodes[0], precision)}\n"
|
| 574 |
+
f" - For n=1: x = (1 * pi) / {k} = {round(antinodes[1], precision)}\n"
|
| 575 |
+
f" - For n=2: x = (2 * pi) / {k} = {round(antinodes[2], precision)}\n\n"
|
| 576 |
+
|
| 577 |
+
f"**Answer:**\n"
|
| 578 |
+
f" - The standing wave equation is y(x,t) = {standing_wave_eq}.\n"
|
| 579 |
+
f" - The first three nodes are at x = {round(nodes[0], precision)}, {round(nodes[1], precision)}, and {round(nodes[2], precision)}.\n"
|
| 580 |
+
f" - The first three antinodes are at x = {round(antinodes[0], precision)}, {round(antinodes[1], precision)}, and {round(antinodes[2], precision)}."
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
return question, solution
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
def main():
|
| 587 |
+
"""
|
| 588 |
+
Generate numerous instances of each waves and phasors template
|
| 589 |
+
with different random seeds and write the results to a JSONL file.
|
| 590 |
+
"""
|
| 591 |
+
import json
|
| 592 |
+
import os
|
| 593 |
+
|
| 594 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 595 |
+
output_file = "testset/electrical_engineering/electromagnetics_and_waves/waves_and_phasors.jsonl"
|
| 596 |
+
|
| 597 |
+
# Create the directory if it doesn't exist
|
| 598 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 599 |
+
|
| 600 |
+
# List of template functions with their ID and level
|
| 601 |
+
templates = [
|
| 602 |
+
(template_wave_parameters_basic, "wave_parameters_basic", "Easy"),
|
| 603 |
+
(template_time_to_phasor, "time_to_phasor", "Easy"),
|
| 604 |
+
(template_wave_equation_interpretation, "wave_equation_interpretation", "Intermediate"),
|
| 605 |
+
(template_phasor_addition, "phasor_addition", "Intermediate"),
|
| 606 |
+
(template_standing_wave_formation, "standing_wave_formation", "Advanced"),
|
| 607 |
+
]
|
| 608 |
+
|
| 609 |
+
# List to store all generated problems
|
| 610 |
+
all_problems = []
|
| 611 |
+
|
| 612 |
+
# Generate problems for each template
|
| 613 |
+
for template_func, id_name, level in templates:
|
| 614 |
+
for _ in range(50):
|
| 615 |
+
# Generate a unique seed for each problem
|
| 616 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 617 |
+
random.seed(seed)
|
| 618 |
+
|
| 619 |
+
# Generate the problem and solution
|
| 620 |
+
question, solution = template_func()
|
| 621 |
+
|
| 622 |
+
# Create a JSON entry
|
| 623 |
+
problem_entry = {
|
| 624 |
+
"seed": seed,
|
| 625 |
+
"branch": "electrical_engineering",
|
| 626 |
+
"domain": "electromagnetics_and_waves",
|
| 627 |
+
"area": "waves_and_phasors",
|
| 628 |
+
"id": id_name,
|
| 629 |
+
"level": level,
|
| 630 |
+
"question": question,
|
| 631 |
+
"solution": solution
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
# Add to the list of problems
|
| 635 |
+
all_problems.append(problem_entry)
|
| 636 |
+
|
| 637 |
+
# Shuffle the problems to mix templates and levels
|
| 638 |
+
random.shuffle(all_problems)
|
| 639 |
+
|
| 640 |
+
# Write all problems to a .jsonl file
|
| 641 |
+
with open(output_file, "w") as file:
|
| 642 |
+
for problem in all_problems:
|
| 643 |
+
file.write(json.dumps(problem))
|
| 644 |
+
file.write("\n")
|
| 645 |
+
|
| 646 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 647 |
+
|
| 648 |
+
|
| 649 |
+
if __name__ == "__main__":
|
| 650 |
+
main()
|
data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/continuous_time_signals.cpython-311.pyc
ADDED
|
Binary file (26.2 kB). View file
|
|
|
data/templates/branches/electrical_engineering/signals_and_systems/__pycache__/discrete_time_signals.cpython-311.pyc
ADDED
|
Binary file (39.3 kB). View file
|
|
|
data/templates/branches/electrical_engineering/signals_and_systems/continuous_time_signals.py
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
from fractions import Fraction
|
| 4 |
+
from data.templates.branches.electrical_engineering.constants import FREQUENCY_RANGE_HZ, AMPLITUDE_RANGE, PHASE_RANGE_DEG, PHASE_RANGE_RAD, OMEGA_MULTIPLIER_RANGE, SAMPLING_FREQ_RANGE_HZ, F0_RANGE_HZ, GAIN_K_RANGE, DELAY_N0_RANGE, DECIMATION_FACTOR_M_RANGE, OMEGA_DENOMINATOR_RANGE
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# Template 1 (Easy)
|
| 8 |
+
def template_nyquist_rate_determination():
|
| 9 |
+
"""
|
| 10 |
+
Nyquist Rate Determination
|
| 11 |
+
|
| 12 |
+
Scenario:
|
| 13 |
+
This template tests the fundamental understanding of the Nyquist-Shannon
|
| 14 |
+
sampling theorem. The user must identify the highest frequency component in a
|
| 15 |
+
continuous-time signal and use it to calculate the minimum sampling rate
|
| 16 |
+
required to prevent aliasing.
|
| 17 |
+
|
| 18 |
+
Core Equations:
|
| 19 |
+
F_nyquist = 2 * F_max
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
tuple: A tuple containing:
|
| 23 |
+
- str: A question asking for the Nyquist rate of a given signal.
|
| 24 |
+
- str: A step-by-step solution.
|
| 25 |
+
"""
|
| 26 |
+
# 1. Parameterize the inputs with random values
|
| 27 |
+
|
| 28 |
+
# Determine the number of sinusoidal components in the signal
|
| 29 |
+
num_components = random.choice([2, 3])
|
| 30 |
+
|
| 31 |
+
# Generate a set of unique, random frequencies to serve as the basis for the problem.
|
| 32 |
+
# Using random.sample ensures all generated frequencies are distinct.
|
| 33 |
+
frequency_pool = range(FREQUENCY_RANGE_HZ[0], FREQUENCY_RANGE_HZ[1])
|
| 34 |
+
frequencies_hz = sorted(random.sample(frequency_pool, num_components), reverse=True)
|
| 35 |
+
|
| 36 |
+
signal_terms = []
|
| 37 |
+
for freq in frequencies_hz:
|
| 38 |
+
# For each frequency, generate a random amplitude, phase, and function type (sin/cos)
|
| 39 |
+
amplitude = round(random.uniform(*AMPLITUDE_RANGE), 1)
|
| 40 |
+
phase_deg = random.randint(*PHASE_RANGE_DEG)
|
| 41 |
+
func_type = random.choice(['cos', 'sin'])
|
| 42 |
+
|
| 43 |
+
# Create the mathematical term as a string
|
| 44 |
+
# Example: "15.5 * cos(2*pi*750*t + 45 deg)"
|
| 45 |
+
term_str = f"{amplitude} * {func_type}(2*pi*{freq}*t + {phase_deg} deg)"
|
| 46 |
+
signal_terms.append(term_str)
|
| 47 |
+
|
| 48 |
+
# Join the individual terms with a " + " to form the full signal equation
|
| 49 |
+
signal_expression = " + ".join(signal_terms)
|
| 50 |
+
|
| 51 |
+
# 2. Perform the core calculation for the solution
|
| 52 |
+
# The highest frequency determines the Nyquist rate.
|
| 53 |
+
f_max = max(frequencies_hz)
|
| 54 |
+
|
| 55 |
+
# The Nyquist rate is twice the maximum frequency.
|
| 56 |
+
f_nyquist = 2 * f_max
|
| 57 |
+
|
| 58 |
+
# 3. Generate the question and solution strings
|
| 59 |
+
question = (
|
| 60 |
+
f"A continuous-time signal is defined as:\n"
|
| 61 |
+
f" x_c(t) = {signal_expression}\n\n"
|
| 62 |
+
f"What is the minimum sampling rate (Fs), also known as the Nyquist rate, "
|
| 63 |
+
f"required to sample this signal without loss of information?"
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
solution = (
|
| 67 |
+
f"**Given Signal:**\n"
|
| 68 |
+
f"x_c(t) = {signal_expression}\n\n"
|
| 69 |
+
|
| 70 |
+
f"**Step 1:** Identify Frequencies\n"
|
| 71 |
+
f"First, we identify all the unique frequency components in the signal's expression. "
|
| 72 |
+
f"By inspecting the '2*pi*F*t' part of each term, we find the frequencies are:\n"
|
| 73 |
+
f"Frequencies = {', '.join(map(str, frequencies_hz))} Hz.\n\n"
|
| 74 |
+
|
| 75 |
+
f"**Step 2:** Find Maximum Frequency\n"
|
| 76 |
+
f"The Nyquist rate is determined by the highest frequency component in the signal. "
|
| 77 |
+
f"We find the maximum value from the list of frequencies.\n"
|
| 78 |
+
f"F_max = max({', '.join(map(str, frequencies_hz))}) = {f_max} Hz.\n\n"
|
| 79 |
+
|
| 80 |
+
f"**Step 3:** Apply Nyquist Theorem\n"
|
| 81 |
+
f"The Nyquist-Shannon sampling theorem states that to avoid aliasing and be able "
|
| 82 |
+
f"to perfectly reconstruct a signal, the sampling rate (Fs) must be at least "
|
| 83 |
+
f"twice its highest frequency component (F_max).\n"
|
| 84 |
+
f"Fs_min = 2 * F_max.\n\n"
|
| 85 |
+
|
| 86 |
+
f"**Step 4:** Calculate the Nyquist Rate\n"
|
| 87 |
+
f"Using the maximum frequency found in Step 2, we calculate the Nyquist rate:\n"
|
| 88 |
+
f"FNyquist = 2 * {f_max} = {f_nyquist} Hz.\n\n"
|
| 89 |
+
|
| 90 |
+
f"**Answer:**\n"
|
| 91 |
+
f"The minimum sampling rate required to sample the signal without loss of "
|
| 92 |
+
f"information is {f_nyquist} Hz."
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
return question, solution
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# Template 2 (Easy)
|
| 99 |
+
def template_continuous_to_discrete_conversion():
|
| 100 |
+
"""
|
| 101 |
+
Continuous-to-Discrete Signal Conversion
|
| 102 |
+
|
| 103 |
+
Scenario:
|
| 104 |
+
This template tests the direct application of the sampling process. The user is
|
| 105 |
+
given a continuous-time sinusoid and a sampling rate and must derive the
|
| 106 |
+
corresponding discrete-time sequence by substituting t=nT. It reinforces the
|
| 107 |
+
relationship between continuous and discrete frequencies (omega = Omega * T).
|
| 108 |
+
|
| 109 |
+
Core Equations:
|
| 110 |
+
x[n] = x_c(nT)
|
| 111 |
+
T = 1 / Fs
|
| 112 |
+
omega = Omega * T
|
| 113 |
+
|
| 114 |
+
Returns:
|
| 115 |
+
tuple: A tuple containing:
|
| 116 |
+
- str: A question asking for the discrete-time representation of a signal.
|
| 117 |
+
- str: A step-by-step solution.
|
| 118 |
+
"""
|
| 119 |
+
# 1. Parameterize the inputs with random values
|
| 120 |
+
amplitude = round(random.uniform(*AMPLITUDE_RANGE), 2)
|
| 121 |
+
|
| 122 |
+
# Generate a continuous-time angular frequency as an integer multiple of pi
|
| 123 |
+
omega_multiplier = random.randint(*OMEGA_MULTIPLIER_RANGE)
|
| 124 |
+
omega_continuous = omega_multiplier * math.pi
|
| 125 |
+
omega_continuous_str = f"{omega_multiplier}*pi"
|
| 126 |
+
|
| 127 |
+
# Randomly choose to express the phase in degrees or radians for variety
|
| 128 |
+
use_degrees = random.choice([True, False])
|
| 129 |
+
if use_degrees:
|
| 130 |
+
phi_deg = random.randint(*PHASE_RANGE_DEG)
|
| 131 |
+
phi_str = f"{phi_deg} deg"
|
| 132 |
+
else:
|
| 133 |
+
phi_rad = round(random.uniform(*PHASE_RANGE_RAD), 2)
|
| 134 |
+
phi_str = f"{phi_rad} rad"
|
| 135 |
+
|
| 136 |
+
# Generate a random sampling frequency
|
| 137 |
+
sampling_freq_hz = random.randint(*SAMPLING_FREQ_RANGE_HZ)
|
| 138 |
+
|
| 139 |
+
# Construct the full continuous-time signal expression for the question
|
| 140 |
+
signal_expression = f"{amplitude} * cos({omega_continuous_str}*t + {phi_str})"
|
| 141 |
+
|
| 142 |
+
# 2. Perform the core calculation for the solution
|
| 143 |
+
sampling_period = 1 / sampling_freq_hz
|
| 144 |
+
|
| 145 |
+
# The discrete-time angular frequency
|
| 146 |
+
omega_discrete = omega_continuous * sampling_period
|
| 147 |
+
|
| 148 |
+
# To create a clean, readable solution, simplify the fraction (multiplier / Fs)
|
| 149 |
+
omega_fraction = Fraction(omega_multiplier, sampling_freq_hz).limit_denominator()
|
| 150 |
+
num = omega_fraction.numerator
|
| 151 |
+
den = omega_fraction.denominator
|
| 152 |
+
|
| 153 |
+
# Format the omega string for the solution
|
| 154 |
+
if den == 1:
|
| 155 |
+
omega_discrete_str = f"{num}*pi" if num != 1 else "pi"
|
| 156 |
+
elif num == 1:
|
| 157 |
+
omega_discrete_str = f"pi/{den}"
|
| 158 |
+
else:
|
| 159 |
+
omega_discrete_str = f"({num}*pi)/{den}"
|
| 160 |
+
|
| 161 |
+
# 3. Generate the question and solution strings
|
| 162 |
+
question = (
|
| 163 |
+
f"A continuous-time signal is given by the expression:\n"
|
| 164 |
+
f"x_c(t) = {signal_expression}\n\n"
|
| 165 |
+
f"This signal is sampled at a frequency of {sampling_freq_hz} Hz. "
|
| 166 |
+
f"Determine the resulting discrete-time signal, x[n], and identify "
|
| 167 |
+
f"its discrete-time angular frequency, omega."
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
solution = (
|
| 171 |
+
f"**Given:**\n"
|
| 172 |
+
f"Continuous-time signal: x_c(t) = {signal_expression}\n"
|
| 173 |
+
f"Sampling frequency: Fs = {sampling_freq_hz} Hz\n\n"
|
| 174 |
+
|
| 175 |
+
f"**Step 1:** State the Sampling Rule\n"
|
| 176 |
+
f"A discrete-time signal x[n] is obtained by replacing the continuous time variable 't' "
|
| 177 |
+
f"with its discrete counterpart 'nT', where T is the sampling period. The rule is:\n"
|
| 178 |
+
f"x[n] = x_c(nT)\n\n"
|
| 179 |
+
|
| 180 |
+
f"**Step 2:** Calculate Sampling Period (T)\n"
|
| 181 |
+
f"The sampling period is the reciprocal of the sampling frequency.\n"
|
| 182 |
+
f"T = 1 / Fs = 1 / {sampling_freq_hz} seconds.\n\n"
|
| 183 |
+
|
| 184 |
+
f"**Step 3:** Substitute t = nT into the Equation\n"
|
| 185 |
+
f"We replace every 't' in the original expression with 'nT'.\n"
|
| 186 |
+
f"x[n] = {amplitude} * cos({omega_continuous_str}*(nT) + {phi_str})\n\n"
|
| 187 |
+
|
| 188 |
+
f"**Step 4:** Identify Discrete Frequency (omega)\n"
|
| 189 |
+
f"Rearrange the terms to match the standard form x[n] = A * cos(omega*n + phi). "
|
| 190 |
+
f"The discrete-time angular frequency, omega, is the coefficient of 'n'.\n"
|
| 191 |
+
f"omega = Omega * T\n"
|
| 192 |
+
f"omega = ({omega_continuous_str}) * (1 / {sampling_freq_hz})\n\n"
|
| 193 |
+
|
| 194 |
+
f"**Step 5:** Calculate and Conclude\n"
|
| 195 |
+
f"Now, we calculate the numerical value for omega and write the final expression.\n"
|
| 196 |
+
f"omega = ({omega_multiplier} / {sampling_freq_hz}) * pi = {omega_discrete_str} rad/sample\n"
|
| 197 |
+
f"The final discrete-time signal is:\n"
|
| 198 |
+
f"x[n] = {amplitude} * cos({omega_discrete_str}*n + {phi_str})\n\n"
|
| 199 |
+
|
| 200 |
+
f"**Answer:**\n"
|
| 201 |
+
f"The resulting discrete-time signal is x[n] = {amplitude} * cos({omega_discrete_str}*n + {phi_str}), "
|
| 202 |
+
f"and its discrete-time angular frequency is omega = {omega_discrete_str} rad/sample."
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
return question, solution
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
# Template 3 (Intermediate)
|
| 209 |
+
def template_aliased_frequency_identification():
|
| 210 |
+
"""
|
| 211 |
+
Identifying Aliased Frequencies
|
| 212 |
+
|
| 213 |
+
Scenario:
|
| 214 |
+
This template assesses the ability to predict the outcome of undersampling (aliasing).
|
| 215 |
+
When a signal is sampled below its Nyquist rate, its frequency appears as a
|
| 216 |
+
lower "folded" frequency. This function generates a problem to calculate this
|
| 217 |
+
apparent frequency.
|
| 218 |
+
|
| 219 |
+
Core Equations:
|
| 220 |
+
Fa = abs(F0 - k * Fs), where k = round(F0 / Fs)
|
| 221 |
+
|
| 222 |
+
Returns:
|
| 223 |
+
tuple: A tuple containing:
|
| 224 |
+
- str: A question asking for the apparent (aliased) frequency.
|
| 225 |
+
- str: A step-by-step solution showing the calculation.
|
| 226 |
+
"""
|
| 227 |
+
# 1. Parameterize the inputs with random values
|
| 228 |
+
|
| 229 |
+
# Generate a random original frequency for the continuous-time signal.
|
| 230 |
+
f0 = random.randint(*F0_RANGE_HZ)
|
| 231 |
+
|
| 232 |
+
# Calculate the corresponding Nyquist rate for this signal.
|
| 233 |
+
f_nyquist = 2 * f0
|
| 234 |
+
|
| 235 |
+
# To guarantee aliasing, generate a sampling frequency (Fs) that is strictly
|
| 236 |
+
# less than the Nyquist rate. We set the range to be between 50% and 95% of
|
| 237 |
+
# the Nyquist rate to create a non-trivial undersampling problem.
|
| 238 |
+
fs = random.randint(int(0.5 * f_nyquist), int(0.95 * f_nyquist))
|
| 239 |
+
|
| 240 |
+
# 2. Perform the core calculation for the solution
|
| 241 |
+
|
| 242 |
+
# Find the integer multiple 'k' which represents the nearest multiple of the
|
| 243 |
+
# sampling frequency to the original frequency.
|
| 244 |
+
k = round(f0 / fs)
|
| 245 |
+
|
| 246 |
+
# Calculate the aliased frequency (Fa) using the folding formula.
|
| 247 |
+
fa = abs(f0 - k * fs)
|
| 248 |
+
|
| 249 |
+
# Standardize the precision for all final outputs for consistency.
|
| 250 |
+
precision = 2
|
| 251 |
+
|
| 252 |
+
# 3. Generate the question and solution strings
|
| 253 |
+
question = (
|
| 254 |
+
f"A continuous-time signal x_c(t) = cos(2*pi*{f0}*t) is sampled at a rate "
|
| 255 |
+
f"of {fs} samples per second.\n\n"
|
| 256 |
+
f"Since this rate is below the Nyquist rate, aliasing occurs. What is the "
|
| 257 |
+
f"apparent frequency, Fa, of the resulting discrete-time signal? "
|
| 258 |
+
f"(The apparent frequency must be in the range 0 <= Fa <= {fs/2} Hz)."
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
solution = (
|
| 262 |
+
f"**Given:**\n"
|
| 263 |
+
f"Original Frequency (F0): {f0} Hz\n"
|
| 264 |
+
f"Sampling Frequency (Fs): {fs} Hz\n\n"
|
| 265 |
+
|
| 266 |
+
f"**Step 1:** State the Condition\n"
|
| 267 |
+
f"The Nyquist rate required to sample this signal without aliasing is "
|
| 268 |
+
f"2 * F0 = 2 * {f0} = {f_nyquist} Hz. "
|
| 269 |
+
f"Since the sampling frequency Fs = {fs} Hz is less than {f_nyquist} Hz, "
|
| 270 |
+
f"the original frequency will be aliased.\n\n"
|
| 271 |
+
|
| 272 |
+
f"**Step 2:** Explain Frequency Folding\n"
|
| 273 |
+
f"When aliasing occurs, the perceived frequency (Fa) is the absolute "
|
| 274 |
+
f"difference between the original frequency and the nearest integer multiple "
|
| 275 |
+
f"of the sampling frequency. The formula is:\n"
|
| 276 |
+
f"Fa = abs(F0 - k * Fs), where 'k' is an integer.\n\n"
|
| 277 |
+
|
| 278 |
+
f"**Step 3:** Find the Correct Multiple (k)\n"
|
| 279 |
+
f"We find the integer 'k' that 'folds' F0 into the principal frequency "
|
| 280 |
+
f"range [0, Fs/2]. This is found by rounding the ratio of F0 to Fs.\n"
|
| 281 |
+
f"k = round(F0 / Fs) = round({f0} / {fs}) = round({round(f0/fs, 4)}) = {k}.\n\n"
|
| 282 |
+
|
| 283 |
+
f"**Step 4:** Calculate Aliased Frequency (Fa)\n"
|
| 284 |
+
f"Now, we compute Fa using the value of k found in the previous step.\n"
|
| 285 |
+
f"Fa = abs({f0} - {k} * {fs})\n"
|
| 286 |
+
f"Fa = abs({f0} - {k * fs})\n"
|
| 287 |
+
f"Fa = {round(fa, precision)} Hz.\n\n"
|
| 288 |
+
|
| 289 |
+
f"**Answer:**\n"
|
| 290 |
+
f"The apparent (aliased) frequency of the resulting signal is {round(fa, precision)} Hz."
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
return question, solution
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
# Template 4 (Intermediate)
|
| 297 |
+
def template_cd_dc_system_analysis():
|
| 298 |
+
"""
|
| 299 |
+
C/D and D/C System Analysis
|
| 300 |
+
|
| 301 |
+
Scenario:
|
| 302 |
+
This template models a full signal processing chain: a continuous-time signal is
|
| 303 |
+
sampled (C/D), processed by a simple discrete-time system (a gain and delay),
|
| 304 |
+
and perfectly reconstructed back to continuous-time (D/C). It tests the
|
| 305 |
+
understanding of how each stage affects the final signal.
|
| 306 |
+
|
| 307 |
+
Core Equations:
|
| 308 |
+
x[n] = x_c(nT)
|
| 309 |
+
y_c(t) is the ideal reconstruction of y[n].
|
| 310 |
+
|
| 311 |
+
Returns:
|
| 312 |
+
tuple: A tuple containing:
|
| 313 |
+
- str: A question describing the system and asking for the final output.
|
| 314 |
+
- str: A step-by-step solution.
|
| 315 |
+
"""
|
| 316 |
+
# 1. Parameterize the inputs with random values
|
| 317 |
+
|
| 318 |
+
# --- Signal Parameters ---
|
| 319 |
+
amplitude = round(random.uniform(*AMPLITUDE_RANGE), 2)
|
| 320 |
+
omega_multiplier = random.randint(*OMEGA_MULTIPLIER_RANGE)
|
| 321 |
+
omega_continuous = omega_multiplier * math.pi
|
| 322 |
+
omega_continuous_str = f"{omega_multiplier}*pi"
|
| 323 |
+
|
| 324 |
+
# --- Sampling Parameters ---
|
| 325 |
+
# Ensure the Nyquist criterion is satisfied by a wide margin.
|
| 326 |
+
# F0 = Omega0 / (2*pi) = (multiplier*pi) / (2*pi) = multiplier / 2.
|
| 327 |
+
# F_nyquist = 2 * F0 = multiplier.
|
| 328 |
+
f_nyquist = omega_multiplier
|
| 329 |
+
# Sample at 3 to 8 times the Nyquist rate to make it a clear "ideal" case.
|
| 330 |
+
sampling_factor = random.randint(3, 8)
|
| 331 |
+
sampling_freq_hz = f_nyquist * sampling_factor
|
| 332 |
+
sampling_period = 1 / sampling_freq_hz
|
| 333 |
+
|
| 334 |
+
# --- System Parameters ---
|
| 335 |
+
gain_k = round(random.uniform(*GAIN_K_RANGE), 2)
|
| 336 |
+
delay_n0 = random.randint(*DELAY_N0_RANGE)
|
| 337 |
+
|
| 338 |
+
# 2. Perform the core calculations for the solution
|
| 339 |
+
|
| 340 |
+
# The final output amplitude is the original amplitude scaled by the system gain.
|
| 341 |
+
output_amplitude = round(amplitude * gain_k, 2)
|
| 342 |
+
|
| 343 |
+
# The total time delay is the discrete sample delay multiplied by the sampling period.
|
| 344 |
+
time_delay_sec = delay_n0 * sampling_period
|
| 345 |
+
|
| 346 |
+
# 3. Generate the question and solution strings
|
| 347 |
+
|
| 348 |
+
# Define the strings for the mathematical expressions
|
| 349 |
+
x_c_t_expr = f"{amplitude} * cos({omega_continuous_str}*t)"
|
| 350 |
+
system_expr = f"y[n] = {gain_k} * x[n - {delay_n0}]"
|
| 351 |
+
|
| 352 |
+
question = (
|
| 353 |
+
f"A continuous-time signal is given by:\n"
|
| 354 |
+
f"x_c(t) = {x_c_t_expr}\n\n"
|
| 355 |
+
f"The signal is sampled with a period T = {sampling_period:.2e} s to produce the "
|
| 356 |
+
f"discrete-time sequence x[n]. This sequence is then processed by a system "
|
| 357 |
+
f"defined by the equation:\n"
|
| 358 |
+
f"{system_expr}\n\n"
|
| 359 |
+
f"If the resulting output sequence, y[n], is converted back to a continuous-time "
|
| 360 |
+
f"signal, y_c(t), using an ideal D/C converter, what is the expression for y_c(t)?"
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
solution = (
|
| 364 |
+
f"**Given:**\n"
|
| 365 |
+
f"Continuous-time signal: x_c(t) = {x_c_t_expr}\n"
|
| 366 |
+
f"Sampling period: T = {sampling_period:.2e} s\n"
|
| 367 |
+
f"Discrete-time system: {system_expr}\n\n"
|
| 368 |
+
|
| 369 |
+
f"**Step 1:** Continuous-to-Discrete (C/D) Conversion\n"
|
| 370 |
+
f"We find the discrete-time signal x[n] by substituting t = nT into x_c(t).\n"
|
| 371 |
+
f"x[n] = x_c(nT) = {amplitude} * cos({omega_continuous_str} * nT)\n\n"
|
| 372 |
+
|
| 373 |
+
f"**Step 2:** Discrete-Time Processing\n"
|
| 374 |
+
f"Next, we apply the system's equation to x[n]. This involves replacing "
|
| 375 |
+
f"x[n] with the expression from Step 1 and shifting it by n0 = {delay_n0} samples.\n"
|
| 376 |
+
f"y[n] = {gain_k} * x[n - {delay_n0}]\n"
|
| 377 |
+
f"y[n] = {gain_k} * ({amplitude} * cos({omega_continuous_str} * (n - {delay_n0})T))\n"
|
| 378 |
+
f"y[n] = {output_amplitude} * cos({omega_continuous_str}*T*(n - {delay_n0}))\n\n"
|
| 379 |
+
|
| 380 |
+
f"**Step 3: ** Discrete-to-Continuous (D/C) Conversion*\n"
|
| 381 |
+
f"An ideal D/C converter maps the discrete-time signal back to a continuous-time "
|
| 382 |
+
f"sinusoid with the original frequency Omega_0 = {omega_continuous_str}. The discrete "
|
| 383 |
+
f"time 'n' is mapped back to continuous time 't' via the relation t = nT.\n"
|
| 384 |
+
f"y_c(t) = {output_amplitude} * cos({omega_continuous_str} * (t - {delay_n0}T))\n"
|
| 385 |
+
f"The term {delay_n0}*T represents a time delay. Let's calculate its value:\n"
|
| 386 |
+
f"Time Delay = {delay_n0} * T = {delay_n0} * {sampling_period:.2e} = {time_delay_sec:.2e} s\n"
|
| 387 |
+
f"Substituting this back, we get the final expression:\n"
|
| 388 |
+
f"y_c(t) = {output_amplitude} * cos({omega_continuous_str}*(t - {time_delay_sec:.2e}))\n\n"
|
| 389 |
+
|
| 390 |
+
f"**Step 4:** Interpret the Result\n"
|
| 391 |
+
f"The output signal y_c(t) is a modified version of the input signal x_c(t). "
|
| 392 |
+
f"Its amplitude has been scaled by a factor of K = {gain_k}, and it has been "
|
| 393 |
+
f"time-delayed by {time_delay_sec:.2e} seconds.\n\n"
|
| 394 |
+
|
| 395 |
+
f"**Answer:**\n"
|
| 396 |
+
f"The final continuous-time output signal is y_c(t) = {output_amplitude} * cos({omega_continuous_str}*(t - {time_delay_sec:.2e}))."
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
return question, solution
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
# Template 5 (Advanced)
|
| 403 |
+
def template_decimation_aliasing_analysis():
|
| 404 |
+
"""
|
| 405 |
+
Decimation (Downsampling) with Aliasing Analysis
|
| 406 |
+
|
| 407 |
+
Scenario:
|
| 408 |
+
This template tests the understanding of decimation and its potential to cause
|
| 409 |
+
aliasing if the signal is not properly band-limited. The generated problem may
|
| 410 |
+
or may not result in aliasing, requiring a full analysis.
|
| 411 |
+
|
| 412 |
+
Core Equations:
|
| 413 |
+
y[n] = x[Mn]
|
| 414 |
+
Aliasing is avoided if omega_0 < pi / M.
|
| 415 |
+
omega_aliased = abs(omega_prime - 2*pi*k), where omega_prime = omega_0 * M.
|
| 416 |
+
|
| 417 |
+
Returns:
|
| 418 |
+
tuple: A tuple containing:
|
| 419 |
+
- str: A multi-part question about the downsampled signal.
|
| 420 |
+
- str: A step-by-step solution with conditional aliasing analysis.
|
| 421 |
+
"""
|
| 422 |
+
# 1. Parameterize the inputs with random values
|
| 423 |
+
M = random.randint(*DECIMATION_FACTOR_M_RANGE)
|
| 424 |
+
|
| 425 |
+
# Randomly decide whether to create a problem that results in aliasing or not.
|
| 426 |
+
will_alias = random.choice([True, False])
|
| 427 |
+
|
| 428 |
+
# Generate an initial frequency omega_0 = (num/den)*pi based on the aliasing choice.
|
| 429 |
+
# The condition for NO aliasing is omega_0 < pi/M, which means num/den < 1/M.
|
| 430 |
+
denominator = random.randint(*OMEGA_DENOMINATOR_RANGE)
|
| 431 |
+
|
| 432 |
+
if not will_alias:
|
| 433 |
+
# We need num/den < 1/M => num < den/M.
|
| 434 |
+
max_numerator = math.floor(denominator / M) - 1
|
| 435 |
+
if max_numerator < 1: max_numerator = 1 # Ensure at least one valid choice
|
| 436 |
+
numerator = random.randint(1, max_numerator)
|
| 437 |
+
else:
|
| 438 |
+
# We need num/den > 1/M => num > den/M.
|
| 439 |
+
min_numerator = math.ceil(denominator / M)
|
| 440 |
+
if min_numerator >= denominator: min_numerator = denominator - 1 # Ensure valid range
|
| 441 |
+
numerator = random.randint(min_numerator, denominator - 1)
|
| 442 |
+
|
| 443 |
+
# The original discrete-time frequency
|
| 444 |
+
omega_0 = (numerator / denominator) * math.pi
|
| 445 |
+
|
| 446 |
+
# --- In-line formatting for omega_0_str ---
|
| 447 |
+
frac_0 = Fraction(numerator, denominator)
|
| 448 |
+
n0, d0 = frac_0.numerator, frac_0.denominator
|
| 449 |
+
if d0 == 1:
|
| 450 |
+
omega_0_str = f"{n0}*pi" if n0 != 1 else "pi"
|
| 451 |
+
elif n0 == 1:
|
| 452 |
+
omega_0_str = f"pi/{d0}"
|
| 453 |
+
else:
|
| 454 |
+
omega_0_str = f"({n0}*pi)/{d0}"
|
| 455 |
+
|
| 456 |
+
# 2. Perform the core calculations for the solution
|
| 457 |
+
aliasing_boundary = math.pi / M
|
| 458 |
+
|
| 459 |
+
# --- In-line formatting for aliasing_boundary_str ---
|
| 460 |
+
aliasing_boundary_str = f"pi/{M}"
|
| 461 |
+
|
| 462 |
+
# The new frequency after substitution before considering the principal range.
|
| 463 |
+
omega_prime = omega_0 * M
|
| 464 |
+
|
| 465 |
+
# --- In-line formatting for omega_prime_str ---
|
| 466 |
+
frac_prime = Fraction(numerator * M, denominator)
|
| 467 |
+
np, dp = frac_prime.numerator, frac_prime.denominator
|
| 468 |
+
if dp == 1:
|
| 469 |
+
omega_prime_str = f"{np}*pi" if np != 1 else "pi"
|
| 470 |
+
elif np == 1:
|
| 471 |
+
omega_prime_str = f"pi/{dp}"
|
| 472 |
+
else:
|
| 473 |
+
omega_prime_str = f"({np}*pi)/{dp}"
|
| 474 |
+
|
| 475 |
+
# Calculate the final perceived frequency.
|
| 476 |
+
if not will_alias:
|
| 477 |
+
final_omega_str = omega_prime_str
|
| 478 |
+
k_val = 0 # No folding needed
|
| 479 |
+
else:
|
| 480 |
+
# The frequency must be folded back into the principal range [-pi, pi].
|
| 481 |
+
k_val = round(omega_prime / (2 * math.pi))
|
| 482 |
+
final_omega = abs(omega_prime - 2 * math.pi * k_val)
|
| 483 |
+
|
| 484 |
+
# --- In-line formatting for final_omega_str ---
|
| 485 |
+
frac_final = Fraction(final_omega / math.pi).limit_denominator(100)
|
| 486 |
+
nf, df = frac_final.numerator, frac_final.denominator
|
| 487 |
+
if df == 1:
|
| 488 |
+
final_omega_str = f"{nf}*pi" if nf != 1 else "pi"
|
| 489 |
+
elif nf == 0:
|
| 490 |
+
final_omega_str = "0"
|
| 491 |
+
elif nf == 1:
|
| 492 |
+
final_omega_str = f"pi/{df}"
|
| 493 |
+
else:
|
| 494 |
+
final_omega_str = f"({nf}*pi)/{df}"
|
| 495 |
+
|
| 496 |
+
# 3. Generate the question and solution strings
|
| 497 |
+
question = (
|
| 498 |
+
f"A discrete-time signal is given by x[n] = cos({omega_0_str}*n).\n"
|
| 499 |
+
f"This signal is downsampled by a factor of M = {M} to produce the signal y[n] = x[Mn].\n\n"
|
| 500 |
+
f"a) Find an expression for the output signal y[n] in the form cos(omega'*n).\n"
|
| 501 |
+
f"b) The condition to avoid aliasing during downsampling is omega_0 < {aliasing_boundary_str}. Based on the given values, did aliasing occur?\n"
|
| 502 |
+
f"c) What is the final perceived angular frequency, omega_a, in the signal y[n]? (The frequency must be in the range 0 <= omega_a <= pi)."
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
# Build the solution string piece by piece
|
| 506 |
+
solution = (
|
| 507 |
+
f"**Given:**\n"
|
| 508 |
+
f"Signal: x[n] = cos({omega_0_str}*n)\n"
|
| 509 |
+
f"Downsampling Factor: M = {M}\n\n"
|
| 510 |
+
|
| 511 |
+
f"**Step 1:** Find the Output Signal Expression\n"
|
| 512 |
+
f"We substitute 'n' with 'Mn' in the original expression for x[n]:\n"
|
| 513 |
+
f" y[n] = x[Mn] = cos({omega_0_str} * (Mn)) = cos(({omega_0_str} * {M}) * n)\n"
|
| 514 |
+
f"The new angular frequency, omega', is:\n"
|
| 515 |
+
f" omega' = {omega_0_str} * {M} = {omega_prime_str}\n"
|
| 516 |
+
f"So, the expression is y[n] = cos({omega_prime_str}*n).\n\n"
|
| 517 |
+
|
| 518 |
+
f"**Step 2:** Check for Aliasing\n"
|
| 519 |
+
f"Aliasing is avoided if the original frequency omega_0 is less than pi/M.\n"
|
| 520 |
+
f"Aliasing Boundary = pi / M = {aliasing_boundary_str} approx {round(aliasing_boundary, 3)}\n"
|
| 521 |
+
f"Original Frequency = {omega_0_str} approx {round(omega_0, 3)}\n"
|
| 522 |
+
f"Comparing the two, we find that {omega_0_str} is {'NOT less' if will_alias else 'less'} than {aliasing_boundary_str}. "
|
| 523 |
+
f"Therefore, aliasing **{'occurred' if will_alias else 'did not occur'}**.\n\n"
|
| 524 |
+
|
| 525 |
+
f"**Step 3:** Find the Final Perceived Frequency (omega_a)\n"
|
| 526 |
+
)
|
| 527 |
+
|
| 528 |
+
if not will_alias:
|
| 529 |
+
solution += (
|
| 530 |
+
f"Since no aliasing occurred, the frequency omega' = {omega_prime_str} is already in the principal range [0, pi].\n"
|
| 531 |
+
f"Therefore, the final perceived frequency is the same.\n"
|
| 532 |
+
f"omega_a = {final_omega_str}"
|
| 533 |
+
)
|
| 534 |
+
else:
|
| 535 |
+
# For clarity, re-calculate final_omega inside the solution string
|
| 536 |
+
final_omega_val = abs(omega_prime - 2 * math.pi * k_val)
|
| 537 |
+
solution += (
|
| 538 |
+
f"Because aliasing occurred, the frequency omega' = {omega_prime_str} is outside the principal range [0, pi] and must be 'folded' back.\n"
|
| 539 |
+
f"We find an integer 'k' such that omega_a = abs(omega' - 2*pi*k) is in the range [0, pi].\n"
|
| 540 |
+
f"k = round(omega' / (2*pi)) = round({omega_prime/math.pi:.2f}*pi / (2*pi)) = round({omega_prime/(2*math.pi):.2f}) = {k_val}\n"
|
| 541 |
+
f"Now we calculate omega_a:\n"
|
| 542 |
+
f"omega_a = abs({omega_prime_str} - 2*pi*{k_val})\n"
|
| 543 |
+
f"omega_a = abs({round(omega_prime, 3)} - {round(2*math.pi*k_val, 3)}) = {round(final_omega_val, 3)}\n"
|
| 544 |
+
f"In terms of pi, this is omega_a = {final_omega_str}.\n\n"
|
| 545 |
+
)
|
| 546 |
+
|
| 547 |
+
solution += (
|
| 548 |
+
f"\n\n**Answer:**\n"
|
| 549 |
+
f"a) The output signal is y[n] = cos({omega_prime_str}*n).\n"
|
| 550 |
+
f"b) Aliasing **{'did' if will_alias else 'did not'}** occur.\n"
|
| 551 |
+
f"c) The final perceived frequency is omega_a = {final_omega_str}."
|
| 552 |
+
)
|
| 553 |
+
|
| 554 |
+
return question, solution
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
def main():
|
| 558 |
+
"""
|
| 559 |
+
Generate numerous instances of each continuous time signals template
|
| 560 |
+
with different random seeds and write the results to a JSONL file.
|
| 561 |
+
"""
|
| 562 |
+
import json
|
| 563 |
+
import os
|
| 564 |
+
|
| 565 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 566 |
+
output_file = "testset/electrical_engineering/signals_and_systems/continuous_time_signals.jsonl"
|
| 567 |
+
|
| 568 |
+
# Create the directory if it doesn't exist
|
| 569 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 570 |
+
|
| 571 |
+
# List of template functions with their ID and level
|
| 572 |
+
templates = [
|
| 573 |
+
(template_nyquist_rate_determination, "nyquist_rate_determination", "Easy"),
|
| 574 |
+
(template_continuous_to_discrete_conversion, "continuous_to_discrete_conversion", "Easy"),
|
| 575 |
+
(template_aliased_frequency_identification, "aliased_frequency_identification", "Intermediate"),
|
| 576 |
+
(template_cd_dc_system_analysis, "cd_dc_system_analysis", "Intermediate"),
|
| 577 |
+
(template_decimation_aliasing_analysis, "decimation_aliasing_analysis", "Advanced"),
|
| 578 |
+
]
|
| 579 |
+
|
| 580 |
+
# List to store all generated problems
|
| 581 |
+
all_problems = []
|
| 582 |
+
|
| 583 |
+
# Generate problems for each template
|
| 584 |
+
for template_func, id_name, level in templates:
|
| 585 |
+
for _ in range(50):
|
| 586 |
+
# Generate a unique seed for each problem
|
| 587 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 588 |
+
random.seed(seed)
|
| 589 |
+
|
| 590 |
+
# Generate the problem and solution
|
| 591 |
+
question, solution = template_func()
|
| 592 |
+
|
| 593 |
+
# Create a JSON entry
|
| 594 |
+
problem_entry = {
|
| 595 |
+
"seed": seed,
|
| 596 |
+
"branch": "electrical_engineering",
|
| 597 |
+
"domain": "signals_and_systems",
|
| 598 |
+
"area": "continuous_time_signals",
|
| 599 |
+
"id": id_name,
|
| 600 |
+
"level": level,
|
| 601 |
+
"question": question,
|
| 602 |
+
"solution": solution
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
# Add to the list of problems
|
| 606 |
+
all_problems.append(problem_entry)
|
| 607 |
+
|
| 608 |
+
# Shuffle the problems to mix templates and levels
|
| 609 |
+
random.shuffle(all_problems)
|
| 610 |
+
|
| 611 |
+
# Write all problems to a .jsonl file
|
| 612 |
+
with open(output_file, "w") as file:
|
| 613 |
+
for problem in all_problems:
|
| 614 |
+
file.write(json.dumps(problem))
|
| 615 |
+
file.write("\n")
|
| 616 |
+
|
| 617 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
if __name__ == "__main__":
|
| 621 |
+
main()
|
data/templates/branches/electrical_engineering/signals_and_systems/discrete_time_signals.py
ADDED
|
@@ -0,0 +1,817 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_signal_operations():
|
| 7 |
+
"""
|
| 8 |
+
Signal Operations: Shifting and Reversal
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template tests the fundamental understanding of transformations on the
|
| 12 |
+
independent variable (time index 'n'). It requires applying a time-shift
|
| 13 |
+
(x[n-n0]) or a time-reversal (x[-n]) to a given finite-length sequence.
|
| 14 |
+
This is a core skill for understanding more complex operations like convolution.
|
| 15 |
+
|
| 16 |
+
Core Equations:
|
| 17 |
+
1. Time-Shift: y[n] = x[n - n0]
|
| 18 |
+
2. Time-Reversal: z[n] = x[-n]
|
| 19 |
+
|
| 20 |
+
Returns:
|
| 21 |
+
tuple: A tuple containing:
|
| 22 |
+
- str: A question asking to perform a time-shift or time-reversal on a sequence.
|
| 23 |
+
- str: A step-by-step solution showing the transformation of the sequence.
|
| 24 |
+
"""
|
| 25 |
+
# 1. Parameterize the inputs with random values
|
| 26 |
+
|
| 27 |
+
# Generate a sequence x[n] as a dictionary {index: value}
|
| 28 |
+
seq_length = random.randint(4, 7)
|
| 29 |
+
# Ensure the origin (n=0) is not at the absolute ends of the sequence
|
| 30 |
+
origin_position = random.randint(1, seq_length - 2)
|
| 31 |
+
start_index = -origin_position
|
| 32 |
+
|
| 33 |
+
x_n = {start_index + i: random.randint(-10, 10) for i in range(seq_length)}
|
| 34 |
+
|
| 35 |
+
# Choose the operation
|
| 36 |
+
operation = random.choice(['shift', 'reversal'])
|
| 37 |
+
|
| 38 |
+
# 2. Perform the core calculation and generate explanatory text
|
| 39 |
+
|
| 40 |
+
if operation == 'shift':
|
| 41 |
+
# Select a non-zero shift amount
|
| 42 |
+
n0 = random.choice([-3, -2, -1, 1, 2, 3])
|
| 43 |
+
output_var = "y"
|
| 44 |
+
|
| 45 |
+
# Core calculation: Shift the keys of the dictionary
|
| 46 |
+
result_n = {k + n0: v for k, v in x_n.items()}
|
| 47 |
+
|
| 48 |
+
# Explanations for the solution
|
| 49 |
+
op_str_symbolic = f"x[n - ({n0})]" if n0 < 0 else f"x[n - {n0}]"
|
| 50 |
+
|
| 51 |
+
direction = "left" if n0 < 0 else "right"
|
| 52 |
+
explanation = (
|
| 53 |
+
f"The operation is a time shift, {output_var}[n] = {op_str_symbolic}.\n"
|
| 54 |
+
f"This corresponds to a shift of the sequence to the {direction} by {abs(n0)} sample(s).\n"
|
| 55 |
+
f"The value at any original index 'k' is moved to a new index 'k + {n0}'."
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
else: # operation == 'reversal'
|
| 59 |
+
n0 = 0 # Not used, but needed for variable scope
|
| 60 |
+
output_var = "z"
|
| 61 |
+
|
| 62 |
+
# Core calculation: Negate the keys of the dictionary
|
| 63 |
+
result_n = {-k: v for k, v in x_n.items()}
|
| 64 |
+
|
| 65 |
+
# Explanations for the solution
|
| 66 |
+
op_str_symbolic = "x[-n]"
|
| 67 |
+
explanation = (
|
| 68 |
+
f"The operation is a time reversal, {output_var}[n] = {op_str_symbolic}.\n"
|
| 69 |
+
f"This corresponds to flipping the sequence about the origin (n=0).\n"
|
| 70 |
+
f"The value at any original index 'k' is moved to a new index '-k'."
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
# Inlined logic to format the input sequence x_n into a string
|
| 74 |
+
if not x_n:
|
| 75 |
+
x_n_str = "{}"
|
| 76 |
+
else:
|
| 77 |
+
min_idx = min(x_n.keys())
|
| 78 |
+
max_idx = max(x_n.keys())
|
| 79 |
+
parts = []
|
| 80 |
+
for i in range(min_idx, max_idx + 1):
|
| 81 |
+
val = x_n.get(i, 0)
|
| 82 |
+
if i == 0:
|
| 83 |
+
parts.append(f"*{val}*") # Asterisk denotes the origin n=0
|
| 84 |
+
else:
|
| 85 |
+
parts.append(str(val))
|
| 86 |
+
x_n_str = f"{{{', '.join(parts)}}}"
|
| 87 |
+
|
| 88 |
+
# Inlined logic to format the result sequence into a string
|
| 89 |
+
if not result_n:
|
| 90 |
+
result_n_str = "{}"
|
| 91 |
+
else:
|
| 92 |
+
min_idx = min(result_n.keys())
|
| 93 |
+
max_idx = max(result_n.keys())
|
| 94 |
+
parts = []
|
| 95 |
+
for i in range(min_idx, max_idx + 1):
|
| 96 |
+
val = result_n.get(i, 0)
|
| 97 |
+
if i == 0:
|
| 98 |
+
parts.append(f"*{val}*")
|
| 99 |
+
else:
|
| 100 |
+
parts.append(str(val))
|
| 101 |
+
result_n_str = f"{{{', '.join(parts)}}}"
|
| 102 |
+
|
| 103 |
+
# 3. Generate the question and solution strings
|
| 104 |
+
|
| 105 |
+
question = (
|
| 106 |
+
f"A discrete-time signal is defined by the sequence:\n"
|
| 107 |
+
f"x[n] = {x_n_str}\n\n"
|
| 108 |
+
f"Determine the resulting sequence, {output_var}[n], after applying the following transformation:\n"
|
| 109 |
+
f"{output_var}[n] = {op_str_symbolic}"
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
# Inlined logic to build the transformation table for the solution
|
| 113 |
+
table_header = f"""| {'Original Index (k)':<20} | {'Original Value x[k]':<22} | {"New Index (k')":<18} | {"New Value y[k')":<20} |"""
|
| 114 |
+
table_separator = "-" * len(table_header)
|
| 115 |
+
|
| 116 |
+
table_rows_list = []
|
| 117 |
+
for k in sorted(x_n.keys()):
|
| 118 |
+
val = x_n[k]
|
| 119 |
+
if operation == 'shift':
|
| 120 |
+
new_k = k + n0
|
| 121 |
+
else: # reversal
|
| 122 |
+
new_k = -k
|
| 123 |
+
table_rows_list.append(f"| {k:<20} | {val:<22} | {new_k:<18} | {val:<20} |")
|
| 124 |
+
|
| 125 |
+
# Combine all parts of the table with newlines
|
| 126 |
+
transformation_table = f"{table_header}\n{table_separator}\n" + "\n".join(table_rows_list)
|
| 127 |
+
|
| 128 |
+
solution = (
|
| 129 |
+
f"**Given:**\n"
|
| 130 |
+
f"The original sequence is x[n] = {x_n_str}.\n"
|
| 131 |
+
f"The operation is {output_var}[n] = {op_str_symbolic}.\n\n"
|
| 132 |
+
|
| 133 |
+
f"**Step 1:** Understand the Transformation\n"
|
| 134 |
+
f"{explanation}\n\n"
|
| 135 |
+
|
| 136 |
+
f"**Step 2:** Apply the Transformation to Each Index\n"
|
| 137 |
+
f"We can create a table to track where each value moves:\n\n"
|
| 138 |
+
f"{transformation_table}\n\n"
|
| 139 |
+
|
| 140 |
+
f"**Step 3:** Construct the Final Sequence\n"
|
| 141 |
+
f"By collecting the values at their new indices, we get the final sequence.\n"
|
| 142 |
+
f"Remember that any index not explicitly calculated has a value of 0.\n\n"
|
| 143 |
+
|
| 144 |
+
f"**Answer:**\n"
|
| 145 |
+
f"The resulting sequence is {output_var}[n] = {result_n_str}"
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
return question, solution
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
# Template 2 (Easy)
|
| 152 |
+
def template_system_properties_memory_causality():
|
| 153 |
+
"""
|
| 154 |
+
System Properties: Memory and Causality
|
| 155 |
+
|
| 156 |
+
Scenario:
|
| 157 |
+
This template tests the ability to analyze a discrete-time system's
|
| 158 |
+
input-output equation to determine two fundamental properties: whether it
|
| 159 |
+
is memoryless and whether it is causal. This requires applying the formal
|
| 160 |
+
definitions of these properties to the given equation.
|
| 161 |
+
|
| 162 |
+
Core Definitions:
|
| 163 |
+
1. Memoryless: Output y[n] depends only on the current input x[n].
|
| 164 |
+
2. Causal: Output y[n] depends only on present and past inputs x[k], where k <= n.
|
| 165 |
+
|
| 166 |
+
Returns:
|
| 167 |
+
tuple: A tuple containing:
|
| 168 |
+
- str: A question asking to determine if a system is memoryless and causal.
|
| 169 |
+
- str: A step-by-step solution analyzing each property with justifications.
|
| 170 |
+
"""
|
| 171 |
+
# 1. Parameterize the inputs by generating a random system type and equation
|
| 172 |
+
|
| 173 |
+
system_type = random.choice(['memoryless_causal', 'memory_causal', 'memory_noncausal'])
|
| 174 |
+
|
| 175 |
+
is_memoryless = False
|
| 176 |
+
is_causal = False
|
| 177 |
+
equation_str = ""
|
| 178 |
+
memory_reason = ""
|
| 179 |
+
causality_reason = ""
|
| 180 |
+
|
| 181 |
+
if system_type == 'memoryless_causal':
|
| 182 |
+
is_memoryless = True
|
| 183 |
+
is_causal = True
|
| 184 |
+
|
| 185 |
+
form = random.choice(['power', 'affine', 'scaled_by_n'])
|
| 186 |
+
if form == 'power':
|
| 187 |
+
power = random.randint(2, 3)
|
| 188 |
+
equation_str = f"(x[n])**{power}"
|
| 189 |
+
memory_reason = f"The output y[n] is the input x[n] raised to the power of {power} at the exact same time 'n'. The system does not need to store any other input values."
|
| 190 |
+
elif form == 'affine':
|
| 191 |
+
gain = random.randint(2, 9)
|
| 192 |
+
offset = random.randint(1, 10)
|
| 193 |
+
equation_str = f"{gain} * x[n] + {offset}"
|
| 194 |
+
memory_reason = f"The output y[n] at time 'n' is a scaled and shifted version of the input x[n] at the exact same time 'n'. No past or future values of the input are needed."
|
| 195 |
+
else: # scaled_by_n
|
| 196 |
+
equation_str = f"n * x[n]"
|
| 197 |
+
memory_reason = "The output y[n] depends on the input x[n] at the same time 'n', scaled by the time index 'n' itself. The time index 'n' is not an input signal value that needs to be stored."
|
| 198 |
+
|
| 199 |
+
causality_reason = "The output y[n] depends only on the input at the present time 'n'. Since no future inputs (k > n) are required, the system is causal."
|
| 200 |
+
|
| 201 |
+
elif system_type == 'memory_causal':
|
| 202 |
+
is_memoryless = False
|
| 203 |
+
is_causal = True
|
| 204 |
+
|
| 205 |
+
delay = random.randint(1, 4)
|
| 206 |
+
form = random.choice(['simple_delay', 'with_present_term'])
|
| 207 |
+
|
| 208 |
+
if form == 'simple_delay':
|
| 209 |
+
equation_str = f"x[n - {delay}]"
|
| 210 |
+
memory_reason = f"To compute the output y[n], the system needs to know the input from {delay} time step(s) in the past, x[n - {delay}]. Therefore, the system must have memory."
|
| 211 |
+
else: # with_present_term
|
| 212 |
+
equation_str = f"x[n] + x[n - {delay}]"
|
| 213 |
+
memory_reason = f"The output y[n] depends on both the current input x[n] and a past input x[n - {delay}]. The system must store or 'remember' the value of x[n - {delay}] to calculate the current output."
|
| 214 |
+
|
| 215 |
+
causality_reason = f"The output y[n] depends on the present input and/or past inputs (up to time n - {delay}). Since it does not depend on any future inputs, the system is causal."
|
| 216 |
+
|
| 217 |
+
else: # system_type == 'memory_noncausal'
|
| 218 |
+
is_memoryless = False
|
| 219 |
+
is_causal = False
|
| 220 |
+
|
| 221 |
+
advance = random.randint(1, 4)
|
| 222 |
+
form = random.choice(['simple_advance', 'with_past_term'])
|
| 223 |
+
|
| 224 |
+
if form == 'simple_advance':
|
| 225 |
+
equation_str = f"x[n + {advance}]"
|
| 226 |
+
causality_reason = f"The output y[n] depends on the input at a future time, x[n + {advance}]. To calculate y[n], the system must know what the input will be {advance} time step(s) in the future. This violates the definition of causality."
|
| 227 |
+
else: # with_past_term
|
| 228 |
+
delay = random.randint(1, 2)
|
| 229 |
+
equation_str = f"x[n - {delay}] + x[n + {advance}]"
|
| 230 |
+
causality_reason = f"The output y[n] depends on a future input, x[n + {advance}]. Even though it also depends on a past input, the dependency on a future value makes the system non-causal."
|
| 231 |
+
|
| 232 |
+
memory_reason = "The output y[n] depends on input values at times other than 'n'. Therefore, the system is not memoryless."
|
| 233 |
+
|
| 234 |
+
# 2. Generate the question and solution strings
|
| 235 |
+
|
| 236 |
+
question = (
|
| 237 |
+
f"A discrete-time system is described by the input-output equation:\n"
|
| 238 |
+
f"y[n] = {equation_str}\n\n"
|
| 239 |
+
f"Determine if the system is:\n"
|
| 240 |
+
f"a) Memoryless\n"
|
| 241 |
+
f"b) Causal\n\n"
|
| 242 |
+
f"Justify your answers based on the definitions of these properties."
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
solution = (
|
| 246 |
+
f"**Given:**\n"
|
| 247 |
+
f"The system's input-output equation is y[n] = {equation_str}.\n\n"
|
| 248 |
+
|
| 249 |
+
f"**Step 1:** Analyze the Memoryless Property\n"
|
| 250 |
+
f"**Definition:** A system is **memoryless** if its output at any time 'n' depends *only* on the input at the same time 'n'.\n"
|
| 251 |
+
f"**Analysis:** {memory_reason}\n"
|
| 252 |
+
f"**Conclusion:** Based on the analysis, the system is **{'memoryless' if is_memoryless else 'not memoryless'}**.\n\n"
|
| 253 |
+
|
| 254 |
+
f"**Step 2:** Analyze the Causality Property\n"
|
| 255 |
+
f"**Definition:** A system is **causal** if its output at any time 'n' depends *only* on the input at the present time 'n' and past times (i.e., on x[k] for k <= n).\n"
|
| 256 |
+
f"**Analysis:** {causality_reason}\n"
|
| 257 |
+
f"**Conclusion:** Based on the analysis, the system is **{'causal' if is_causal else 'not causal'}**.\n\n"
|
| 258 |
+
|
| 259 |
+
f"**Answer:**\n"
|
| 260 |
+
f"a) Memoryless: **{'Yes' if is_memoryless else 'No'}**\n"
|
| 261 |
+
f"b) Causal: **{'Yes' if is_causal else 'No'}**"
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
return question, solution
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
# Template 3 (Intermediate)
|
| 268 |
+
def template_finite_convolution():
|
| 269 |
+
"""
|
| 270 |
+
Convolution of Finite-Length Sequences
|
| 271 |
+
|
| 272 |
+
Scenario:
|
| 273 |
+
This template tests the direct application of the convolution sum, which is
|
| 274 |
+
the fundamental operation for determining the output of a Linear
|
| 275 |
+
Time-Invariant (LTI) system. It requires convolving two finite-length
|
| 276 |
+
sequences, representing an input signal and a system's impulse response.
|
| 277 |
+
|
| 278 |
+
Core Equations:
|
| 279 |
+
1. Convolution Sum: y[n] = sum(x[k] * h[n-k]) for all k
|
| 280 |
+
|
| 281 |
+
Returns:
|
| 282 |
+
tuple: A tuple containing:
|
| 283 |
+
- str: A question asking to find the output of an LTI system for a given input.
|
| 284 |
+
- str: A step-by-step solution demonstrating the convolution process.
|
| 285 |
+
"""
|
| 286 |
+
# 1. Parameterize the inputs with random values
|
| 287 |
+
|
| 288 |
+
# Generate sequence x[n] as a dictionary {index: value}
|
| 289 |
+
x_len = random.randint(3, 4)
|
| 290 |
+
x_origin_pos = random.randint(0, x_len - 1)
|
| 291 |
+
x_start_idx = -x_origin_pos
|
| 292 |
+
x_n = {x_start_idx + i: random.randint(-3, 3) for i in range(x_len)}
|
| 293 |
+
# Ensure the sequence isn't all zeros
|
| 294 |
+
if all(v == 0 for v in x_n.values()):
|
| 295 |
+
x_n[random.choice(list(x_n.keys()))] = random.randint(1, 3)
|
| 296 |
+
|
| 297 |
+
# Generate sequence h[n] as a dictionary {index: value}
|
| 298 |
+
h_len = random.randint(3, 4)
|
| 299 |
+
h_origin_pos = random.randint(0, h_len - 1)
|
| 300 |
+
h_start_idx = -h_origin_pos
|
| 301 |
+
h_n = {h_start_idx + i: random.randint(-2, 2) for i in range(h_len)}
|
| 302 |
+
if all(v == 0 for v in h_n.values()):
|
| 303 |
+
h_n[random.choice(list(h_n.keys()))] = random.randint(1, 2)
|
| 304 |
+
|
| 305 |
+
# Inlined logic to format x_n into a string
|
| 306 |
+
min_idx_x = min(x_n.keys())
|
| 307 |
+
max_idx_x = max(x_n.keys())
|
| 308 |
+
x_parts = []
|
| 309 |
+
for i in range(min_idx_x, max_idx_x + 1):
|
| 310 |
+
val = x_n.get(i, 0)
|
| 311 |
+
x_parts.append(f"*{val}*" if i == 0 else str(val))
|
| 312 |
+
x_n_str = f"{{{', '.join(x_parts)}}}"
|
| 313 |
+
|
| 314 |
+
# Inlined logic to format h_n into a string
|
| 315 |
+
min_idx_h = min(h_n.keys())
|
| 316 |
+
max_idx_h = max(h_n.keys())
|
| 317 |
+
h_parts = []
|
| 318 |
+
for i in range(min_idx_h, max_idx_h + 1):
|
| 319 |
+
val = h_n.get(i, 0)
|
| 320 |
+
h_parts.append(f"*{val}*" if i == 0 else str(val))
|
| 321 |
+
h_n_str = f"{{{', '.join(h_parts)}}}"
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
# 2. Perform the core calculation (Convolution)
|
| 325 |
+
|
| 326 |
+
y_n = {}
|
| 327 |
+
y_start_idx = min(x_n.keys()) + min(h_n.keys())
|
| 328 |
+
y_end_idx = max(x_n.keys()) + max(h_n.keys())
|
| 329 |
+
|
| 330 |
+
all_k_indices = sorted(list(x_n.keys()))
|
| 331 |
+
|
| 332 |
+
for n in range(y_start_idx, y_end_idx + 1):
|
| 333 |
+
current_sum = 0
|
| 334 |
+
for k in all_k_indices:
|
| 335 |
+
x_k = x_n.get(k, 0)
|
| 336 |
+
h_nk = h_n.get(n - k, 0)
|
| 337 |
+
current_sum += x_k * h_nk
|
| 338 |
+
# Only store non-zero values, but the final string will show zeros in the range
|
| 339 |
+
if current_sum != 0:
|
| 340 |
+
y_n[n] = current_sum
|
| 341 |
+
|
| 342 |
+
# 3. Generate the question and solution strings
|
| 343 |
+
|
| 344 |
+
question = (
|
| 345 |
+
f"An LTI system has an impulse response h[n] = {h_n_str}.\n\n"
|
| 346 |
+
f"Determine the system's output, y[n] = x[n] * h[n], for the input x[n] = {x_n_str}."
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
# Build the detailed calculation steps for the solution
|
| 350 |
+
calculation_steps = []
|
| 351 |
+
y_indices_to_show = sorted(y_n.keys()) if y_n else [y_start_idx]
|
| 352 |
+
|
| 353 |
+
# To avoid too much output, only show a few sample calculations
|
| 354 |
+
if len(y_indices_to_show) > 3:
|
| 355 |
+
# Show first, middle, and last non-zero points
|
| 356 |
+
middle_index = y_indices_to_show[len(y_indices_to_show)//2]
|
| 357 |
+
y_indices_to_show = [y_indices_to_show[0], middle_index, y_indices_to_show[-1]]
|
| 358 |
+
|
| 359 |
+
for n in y_indices_to_show:
|
| 360 |
+
step_str = f"For n = {n}:\n"
|
| 361 |
+
step_str += f"y[{n}] = sum( x[k] * h[{n}-k] )\n"
|
| 362 |
+
|
| 363 |
+
sum_expr_terms = []
|
| 364 |
+
val_expr_terms = []
|
| 365 |
+
|
| 366 |
+
# Determine the range of k where overlap can occur for clarity
|
| 367 |
+
k_min = min(x_n.keys())
|
| 368 |
+
k_max = max(x_n.keys())
|
| 369 |
+
|
| 370 |
+
for k in range(k_min, k_max + 1):
|
| 371 |
+
x_val = x_n.get(k, 0)
|
| 372 |
+
# Only show terms where x[k] contributes to the expression
|
| 373 |
+
if x_val != 0:
|
| 374 |
+
h_val = h_n.get(n - k, 0)
|
| 375 |
+
sum_expr_terms.append(f"x[{k}]h[{n-k}]")
|
| 376 |
+
val_expr_terms.append(f"({x_val})({h_val})")
|
| 377 |
+
|
| 378 |
+
step_str += f"y[{n}] = {' + '.join(sum_expr_terms)}\n"
|
| 379 |
+
step_str += f"y[{n}] = {' + '.join(val_expr_terms)}\n"
|
| 380 |
+
step_str += f"y[{n}] = {y_n.get(n, 0)}\n"
|
| 381 |
+
calculation_steps.append(step_str)
|
| 382 |
+
|
| 383 |
+
# Inlined logic to format the final sequence y_n
|
| 384 |
+
if not y_n:
|
| 385 |
+
y_n_str = "{*0*}"
|
| 386 |
+
else:
|
| 387 |
+
# The full range of y[n] must be shown, including zeros between start and end
|
| 388 |
+
min_idx_y = y_start_idx
|
| 389 |
+
max_idx_y = y_end_idx
|
| 390 |
+
y_parts = []
|
| 391 |
+
for i in range(min_idx_y, max_idx_y + 1):
|
| 392 |
+
val = y_n.get(i, 0)
|
| 393 |
+
y_parts.append(f"*{val}*" if i == 0 else str(val))
|
| 394 |
+
y_n_str = f"{{{', '.join(y_parts)}}}"
|
| 395 |
+
|
| 396 |
+
y_values_list = [f" y[{n}] = {y_n.get(n, 0)}" for n in range(y_start_idx, y_end_idx + 1)]
|
| 397 |
+
y_values_str = "\n".join(y_values_list)
|
| 398 |
+
|
| 399 |
+
calculation_steps_str = "\n".join(calculation_steps)
|
| 400 |
+
|
| 401 |
+
solution = (
|
| 402 |
+
f"**Given:**\n"
|
| 403 |
+
f"Input Signal: x[n] = {x_n_str}\n"
|
| 404 |
+
f"Impulse Response: h[n] = {h_n_str}\n\n"
|
| 405 |
+
|
| 406 |
+
f"**Step 1:** State the Convolution Formula\n"
|
| 407 |
+
f"The output y[n] of an LTI system is the convolution of the input x[n] with the impulse response h[n]. The convolution sum is defined as:\n"
|
| 408 |
+
f"y[n] = sum over all k of (x[k] * h[n-k])\n\n"
|
| 409 |
+
|
| 410 |
+
f"**Step 2:** Apply the Flip-and-Slide Method\n"
|
| 411 |
+
f"We can visualize this process by flipping the impulse response h[k] to get h[-k], and then sliding it by 'n' positions. For each slide 'n', we calculate the sum of the products of the overlapping samples.\n\n"
|
| 412 |
+
f"Let's calculate a few points:\n\n"
|
| 413 |
+
f"{calculation_steps_str}\n"
|
| 414 |
+
|
| 415 |
+
f"**Step 3: Calculate All Output Values**\n"
|
| 416 |
+
f"By continuing this process for all values of 'n' where the sequences could overlap (from n={y_start_idx} to n={y_end_idx}), we get the following values for the output:\n"
|
| 417 |
+
f"{y_values_str}\n\n"
|
| 418 |
+
|
| 419 |
+
f"**Answer:**\n"
|
| 420 |
+
f"The complete output sequence is y[n] = {y_n_str}"
|
| 421 |
+
)
|
| 422 |
+
|
| 423 |
+
return question, solution
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
# Template 4 (Intermediate)
|
| 427 |
+
def template_system_property_linearity():
|
| 428 |
+
"""
|
| 429 |
+
System Properties: Linearity
|
| 430 |
+
|
| 431 |
+
Scenario:
|
| 432 |
+
This template tests the ability to formally prove or disprove if a system
|
| 433 |
+
is linear by checking the two defining properties: additivity and
|
| 434 |
+
homogeneity (also known as scaling). This is a fundamental concept in
|
| 435 |
+
characterizing systems.
|
| 436 |
+
|
| 437 |
+
Core Definitions:
|
| 438 |
+
1. Additivity: T{x1[n] + x2[n]} = T{x1[n]} + T{x2[n]}
|
| 439 |
+
2. Homogeneity: T{a * x[n]} = a * T{x[n]}
|
| 440 |
+
|
| 441 |
+
Returns:
|
| 442 |
+
tuple: A tuple containing:
|
| 443 |
+
- str: A question asking to determine if a system is linear.
|
| 444 |
+
- str: A step-by-step solution showing the formal proof.
|
| 445 |
+
"""
|
| 446 |
+
# 1. Parameterize the inputs by generating a random system type and equation
|
| 447 |
+
|
| 448 |
+
system_type = random.choice(['linear_gain', 'linear_delay', 'nonlinear_offset', 'nonlinear_power'])
|
| 449 |
+
|
| 450 |
+
# These variables will be populated based on the system type
|
| 451 |
+
equation_str = ""
|
| 452 |
+
is_linear = False
|
| 453 |
+
|
| 454 |
+
# Strings for each step of the proof
|
| 455 |
+
add_y1y2_sum_str = ""
|
| 456 |
+
add_y3_str = ""
|
| 457 |
+
add_comparison_str = ""
|
| 458 |
+
|
| 459 |
+
hom_ay1_str = ""
|
| 460 |
+
hom_ya_str = ""
|
| 461 |
+
hom_comparison_str = ""
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
if system_type == 'linear_gain':
|
| 465 |
+
is_linear = True
|
| 466 |
+
k = random.randint(1, 10)
|
| 467 |
+
equation_str = f"{k} * x[n]"
|
| 468 |
+
|
| 469 |
+
# Additivity Proof Strings
|
| 470 |
+
add_y1y2_sum_str = f"y1[n] + y2[n] = ({k} * x1[n]) + ({k} * x2[n]) = {k} * (x1[n] + x2[n])"
|
| 471 |
+
add_y3_str = f"y3[n] = {k} * (x3[n]) = {k} * (x1[n] + x2[n])"
|
| 472 |
+
add_comparison_str = "Since y3[n] is equal to y1[n] + y2[n], the system satisfies the additivity property."
|
| 473 |
+
|
| 474 |
+
# Homogeneity Proof Strings
|
| 475 |
+
hom_ay1_str = f"a * y1[n] = a * ({k} * x1[n])"
|
| 476 |
+
hom_ya_str = f"ya[n] = {k} * (xa[n]) = {k} * (a * x1[n])"
|
| 477 |
+
hom_comparison_str = "Since ya[n] is equal to a * y1[n], the system satisfies the homogeneity property."
|
| 478 |
+
|
| 479 |
+
elif system_type == 'linear_delay':
|
| 480 |
+
is_linear = True
|
| 481 |
+
d = random.randint(1, 10)
|
| 482 |
+
equation_str = f"x[n - {d}]"
|
| 483 |
+
|
| 484 |
+
# Additivity Proof Strings
|
| 485 |
+
add_y1y2_sum_str = f"y1[n] + y2[n] = x1[n - {d}] + x2[n - {d}]"
|
| 486 |
+
add_y3_str = f"y3[n] = x3[n - {d}] = x1[n - {d}] + x2[n - {d}]"
|
| 487 |
+
add_comparison_str = "Since y3[n] is equal to y1[n] + y2[n], the system satisfies the additivity property."
|
| 488 |
+
|
| 489 |
+
# Homogeneity Proof Strings
|
| 490 |
+
hom_ay1_str = f"a * y1[n] = a * x1[n - {d}]"
|
| 491 |
+
hom_ya_str = f"ya[n] = xa[n - {d}] = a * x1[n - {d}]"
|
| 492 |
+
hom_comparison_str = "Since ya[n] is equal to a * y1[n], the system satisfies the homogeneity property."
|
| 493 |
+
|
| 494 |
+
elif system_type == 'nonlinear_offset':
|
| 495 |
+
is_linear = False
|
| 496 |
+
C = random.randint(1, 5) * random.choice([-1, 1])
|
| 497 |
+
C_str = f"+ {C}" if C > 0 else f"- {abs(C)}"
|
| 498 |
+
equation_str = f"x[n] {C_str}"
|
| 499 |
+
|
| 500 |
+
# Additivity Proof Strings
|
| 501 |
+
add_y1y2_sum_str = f"y1[n] + y2[n] = (x1[n] {C_str}) + (x2[n] {C_str}) = x1[n] + x2[n] + {2*C}"
|
| 502 |
+
add_y3_str = f"y3[n] = (x3[n]) {C_str} = (x1[n] + x2[n]) {C_str}"
|
| 503 |
+
add_comparison_str = f"Since x1[n] + x2[n] + {2*C} is not equal to x1[n] + x2[n] {C_str}, the system fails the additivity test."
|
| 504 |
+
|
| 505 |
+
# Homogeneity Proof Strings
|
| 506 |
+
hom_ay1_str = f"a * y1[n] = a * (x1[n] {C_str}) = a*x1[n] + {C}*a"
|
| 507 |
+
hom_ya_str = f"ya[n] = (xa[n]) {C_str} = (a * x1[n]) {C_str}"
|
| 508 |
+
hom_comparison_str = f"Since a*x1[n] + {C}*a is not equal to a*x1[n] {C_str} (for a != 1), the system fails the homogeneity test."
|
| 509 |
+
|
| 510 |
+
elif system_type == 'nonlinear_power':
|
| 511 |
+
is_linear = False
|
| 512 |
+
p = random.randint(2, 3)
|
| 513 |
+
equation_str = f"(x[n])**{p}"
|
| 514 |
+
|
| 515 |
+
# Additivity Proof Strings
|
| 516 |
+
add_y1y2_sum_str = f"y1[n] + y2[n] = (x1[n])**{p} + (x2[n])**{p}"
|
| 517 |
+
add_y3_str = f"y3[n] = (x3[n])**{p} = (x1[n] + x2[n])**{p}"
|
| 518 |
+
add_comparison_str = f"In general, (x1[n] + x2[n])**{p} is not equal to (x1[n])**{p} + (x2[n])**{p}. Therefore, the system fails the additivity test."
|
| 519 |
+
|
| 520 |
+
# Homogeneity Proof Strings
|
| 521 |
+
hom_ay1_str = f"a * y1[n] = a * (x1[n])**{p}"
|
| 522 |
+
hom_ya_str = f"ya[n] = (xa[n])**{p} = (a * x1[n])**{p} = (a**{p}) * (x1[n])**{p}"
|
| 523 |
+
hom_comparison_str = f"Since a * (x1[n])**{p} is not equal to (a**{p}) * (x1[n])**{p} (for a != 1), the system fails the homogeneity test."
|
| 524 |
+
|
| 525 |
+
# 2. Generate the question and solution strings
|
| 526 |
+
|
| 527 |
+
question = (
|
| 528 |
+
f"A discrete-time system is governed by the equation:\n"
|
| 529 |
+
f"y[n] = {equation_str}\n\n"
|
| 530 |
+
f"Determine if this system is linear by testing the properties of additivity and homogeneity."
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
solution = (
|
| 534 |
+
f"**Given:**\n"
|
| 535 |
+
f"The system equation is y[n] = {equation_str}.\n\n"
|
| 536 |
+
|
| 537 |
+
f"**Step 1:** State the Conditions for Linearity\n"
|
| 538 |
+
f"For a system to be linear, it must satisfy two properties:\n"
|
| 539 |
+
f"1. **Additivity:** T{{x1[n] + x2[n]}} = T{{x1[n]}} + T{{x2[n]}}\n"
|
| 540 |
+
f"2. **Homogeneity (Scaling):** T{{a*x[n]}} = a*T{{x[n]}}\n"
|
| 541 |
+
f"We must test both properties.\n\n"
|
| 542 |
+
|
| 543 |
+
f"**Step 2:** Test for Additivity\n"
|
| 544 |
+
f"Let's define two arbitrary inputs, x1[n] and x2[n]. The corresponding outputs are:\n"
|
| 545 |
+
f"y1[n] = {equation_str.replace('x[n]', 'x1[n]')}\n"
|
| 546 |
+
f"y2[n] = {equation_str.replace('x[n]', 'x2[n]')}\n\n"
|
| 547 |
+
f"The sum of these outputs is:\n"
|
| 548 |
+
f"y1[n] + y2[n] = {add_y1y2_sum_str}\n\n"
|
| 549 |
+
f"Now, let's define a third input x3[n] = x1[n] + x2[n]. The output y3[n] is:\n"
|
| 550 |
+
f"y3[n] = {add_y3_str}\n\n"
|
| 551 |
+
f"**Comparison:** {add_comparison_str}\n\n"
|
| 552 |
+
|
| 553 |
+
f"**Step 3:** Test for Homogeneity (Scaling)\n"
|
| 554 |
+
f"Let's define an input x1[n] and a constant 'a'. The output is y1[n] = {equation_str.replace('x[n]', 'x1[n]')}.\n"
|
| 555 |
+
f"The scaled output is:\n"
|
| 556 |
+
f"a * y1[n] = {hom_ay1_str}\n\n"
|
| 557 |
+
f"Now, let's define a new input xa[n] = a * x1[n]. The output ya[n] is:\n"
|
| 558 |
+
f"ya[n] = {hom_ya_str}\n\n"
|
| 559 |
+
f"**Comparison:** {hom_comparison_str}\n\n"
|
| 560 |
+
|
| 561 |
+
f"**Answer:**\n"
|
| 562 |
+
f"{'The system satisfies both additivity and homogeneity, therefore, the system is **linear**.' if is_linear else 'The system fails at least one of the tests (additivity or homogeneity), therefore, the system is **not linear**.'}"
|
| 563 |
+
)
|
| 564 |
+
|
| 565 |
+
return question, solution
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
# Template 5 (Advanced)
|
| 569 |
+
def template_impulse_response_from_lccde():
|
| 570 |
+
"""
|
| 571 |
+
Finding the Impulse Response from a Difference Equation (Advanced)
|
| 572 |
+
|
| 573 |
+
Scenario:
|
| 574 |
+
This template tests the ability to find the impulse response h[n] for a
|
| 575 |
+
system described by a Linear Constant-Coefficient Difference Equation
|
| 576 |
+
(LCCDE). The process involves setting the input to the unit impulse,
|
| 577 |
+
delta[n], and solving the resulting recurrence relation for h[n] by finding
|
| 578 |
+
initial conditions and then solving the homogeneous equation.
|
| 579 |
+
|
| 580 |
+
Core Definitions:
|
| 581 |
+
1. Impulse Response: h[n] = T{delta[n]}
|
| 582 |
+
2. Causality: h[n] = 0 for n < 0
|
| 583 |
+
|
| 584 |
+
Returns:
|
| 585 |
+
tuple: A tuple containing:
|
| 586 |
+
- str: A question asking for the impulse response of a system given its LCCDE.
|
| 587 |
+
- str: A step-by-step solution detailing the recursive method.
|
| 588 |
+
"""
|
| 589 |
+
# 1. Parameterize by randomly choosing system order and coefficients
|
| 590 |
+
|
| 591 |
+
order = random.choice(['first', 'second'])
|
| 592 |
+
|
| 593 |
+
# Generate coefficients with small integer values for clarity
|
| 594 |
+
b0 = random.randint(1, 4) * random.choice([-1, 1])
|
| 595 |
+
a1 = random.randint(1, 5) * random.choice([-1, 1])
|
| 596 |
+
|
| 597 |
+
# Helper lambda to format coefficients into strings like "+ 3" or "- 2"
|
| 598 |
+
fmt = lambda c, v: f"+ {c}" if c > 0 else f"- {abs(c)}"
|
| 599 |
+
|
| 600 |
+
if order == 'first':
|
| 601 |
+
# For first order, we can have b0*x[n] and b1*x[n-1] terms
|
| 602 |
+
b1 = random.randint(1, 4) * random.choice([-1, 1])
|
| 603 |
+
|
| 604 |
+
# Build equation strings
|
| 605 |
+
y_terms = f"y[n] {fmt(a1, 'y[n-1]')}y[n-1]"
|
| 606 |
+
# Randomly omit b1 term for variety
|
| 607 |
+
if random.random() < 0.4:
|
| 608 |
+
b1 = 0
|
| 609 |
+
x_terms = f"{b0}x[n]"
|
| 610 |
+
if b1 != 0:
|
| 611 |
+
x_terms += f" {fmt(b1, 'x[n-1]')}x[n-1]"
|
| 612 |
+
|
| 613 |
+
equation_str = f"{y_terms} = {x_terms}"
|
| 614 |
+
|
| 615 |
+
# 2. Perform the core calculation for a first-order system
|
| 616 |
+
h0 = b0
|
| 617 |
+
h1 = b1 - a1 * h0
|
| 618 |
+
|
| 619 |
+
term1_str = f"{h0}*delta[n]"
|
| 620 |
+
|
| 621 |
+
h_decay_expr = ""
|
| 622 |
+
if h1 != 0:
|
| 623 |
+
base = -a1
|
| 624 |
+
# Use fmt helper to correctly sign the term
|
| 625 |
+
h_decay_expr = f" {fmt(h1, '')}({base})**(n-1) * u[n-1]"
|
| 626 |
+
|
| 627 |
+
final_h_n = f"{term1_str}{h_decay_expr}"
|
| 628 |
+
|
| 629 |
+
# 3. Generate the solution string for a first-order system
|
| 630 |
+
solution_steps = (
|
| 631 |
+
f"**Step 3:** Solve Recursively for Initial Conditions\n"
|
| 632 |
+
f"We assume the system is causal, so **h[n] = 0 for n < 0**.\n\n"
|
| 633 |
+
f"**For n = 0:**\n"
|
| 634 |
+
f"h[0] {fmt(a1, 'h[-1]')}h[-1] = {b0}*delta[0] {'+ 0' if b1==0 else ' ' + fmt(b1, 'delta[-1]')+'*delta[-1]'}\n"
|
| 635 |
+
f"h[0] {fmt(a1, '0')}*(0) = {b0}*(1) + {b1}*(0)\n"
|
| 636 |
+
f"**h[0] = {h0}**\n\n"
|
| 637 |
+
|
| 638 |
+
f"**For n = 1:**\n"
|
| 639 |
+
f"h[1] {fmt(a1, 'h[0]')}h[0] = {b0}*delta[1] {fmt(b1, 'delta[0]')}*delta[0]\n"
|
| 640 |
+
f"h[1] {fmt(a1, h0)}*({h0}) = {b0}*(0) + {b1}*(1)\n"
|
| 641 |
+
f"h[1] = {b1} - ({a1*h0})\n"
|
| 642 |
+
f"**h[1] = {h1}**\n\n"
|
| 643 |
+
|
| 644 |
+
f"**Step 4:** Find the Homogeneous Solution\n"
|
| 645 |
+
f"For n >= 2, the input delta terms are zero. The equation becomes homogeneous:\n"
|
| 646 |
+
f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] = 0 => h[n] = {-a1}*h[n-1]\n\n"
|
| 647 |
+
f"The solution to this recurrence for n >= 1 is a decaying exponential that starts at n=1 with value h[1].\n"
|
| 648 |
+
f"This part of the response can be written as h[1]*({-a1})**(n-1)*u[n-1].\n\n"
|
| 649 |
+
|
| 650 |
+
f"**Step 5:** Combine Results for the Final Expression\n"
|
| 651 |
+
f"The total impulse response is the sum of the value at n=0 and the response for n >= 1:\n"
|
| 652 |
+
f"h[n] = h[0]*delta[n] + h[1]*({-a1})**(n-1)*u[n-1]\n"
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
+
else: # order == 'second'
|
| 656 |
+
# To guarantee real, distinct roots: D = a1^2 - 4*a2 > 0
|
| 657 |
+
a2 = random.randint(-5, 5)
|
| 658 |
+
if a2 == 0: a2 = 1 # Avoid trivial case
|
| 659 |
+
while a1**2 - 4*a2 <= 0:
|
| 660 |
+
a1 = random.randint(-6, 6)
|
| 661 |
+
if a1 == 0: a1 = 1
|
| 662 |
+
|
| 663 |
+
# Randomize RHS for increased diversity
|
| 664 |
+
b1 = random.randint(-3, 3) if random.random() > 0.4 else 0
|
| 665 |
+
b2 = random.randint(-2, 2) if random.random() > 0.3 else 0
|
| 666 |
+
|
| 667 |
+
# Build equation strings
|
| 668 |
+
x_terms = f"{b0}x[n]"
|
| 669 |
+
if b1 != 0: x_terms += f" {fmt(b1, 'x[n-1]')}x[n-1]"
|
| 670 |
+
if b2 != 0: x_terms += f" {fmt(b2, 'x[n-2]')}x[n-2]"
|
| 671 |
+
equation_str = f"y[n] {fmt(a1, 'y[n-1]')}y[n-1] {fmt(a2, 'y[n-2]')}y[n-2] = {x_terms}"
|
| 672 |
+
|
| 673 |
+
# 2. Perform the core calculation for a second-order system
|
| 674 |
+
h0 = b0
|
| 675 |
+
h1 = b1 - a1 * h0
|
| 676 |
+
|
| 677 |
+
# Correctly solve r^2 + a1*r + a2 = 0
|
| 678 |
+
discriminant = a1**2 - 4*a2
|
| 679 |
+
r1 = (-a1 + math.sqrt(discriminant)) / 2
|
| 680 |
+
r2 = (-a1 - math.sqrt(discriminant)) / 2
|
| 681 |
+
r1_str, r2_str = f"{r1:.2f}", f"{r2:.2f}"
|
| 682 |
+
|
| 683 |
+
# Correctly solve for C1 and C2 with general h[0], h[1]
|
| 684 |
+
if abs(r1 - r2) < 1e-9: # Failsafe for very close roots
|
| 685 |
+
r1, r2 = round(r1, 2), round(r2, 2)
|
| 686 |
+
C1 = (h1 - h0 * r2) / (r1 - r2)
|
| 687 |
+
C2 = (h0 * r1 - h1) / (r1 - r2)
|
| 688 |
+
C1_str, C2_str = f"{C1:.2f}", f"{C2:.2f}"
|
| 689 |
+
|
| 690 |
+
final_h_n = f"({C1_str}({r1_str})**n {fmt(C2, C2_str)}({r2_str})**n) * u[n]"
|
| 691 |
+
|
| 692 |
+
# 3. Generate the solution string for a second-order system
|
| 693 |
+
h_eq = f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] {fmt(a2, 'h[n-2]')}h[n-2]"
|
| 694 |
+
d_eq = f"{b0}*delta[n]"
|
| 695 |
+
if b1 != 0: d_eq += f" {fmt(b1, 'd[n-1]')}*delta[n-1]"
|
| 696 |
+
if b2 != 0: d_eq += f" {fmt(b2, 'd[n-2]')}*delta[n-2]"
|
| 697 |
+
|
| 698 |
+
solution_steps = (
|
| 699 |
+
f"**Step 3:** Solve Recursively for Initial Conditions\n"
|
| 700 |
+
f"We assume the system is causal, so **h[n] = 0 for n < 0**.\n\n"
|
| 701 |
+
f"**For n = 0:**\n"
|
| 702 |
+
f"h[0] {fmt(a1, 'h[-1]')}h[-1] {fmt(a2, 'h[-2]')}h[-2] = {b0}*delta[0] ... (other delta terms are 0)\n"
|
| 703 |
+
f"h[0] {fmt(a1, '0')}*(0) {fmt(a2, '0')}*(0) = {b0}*(1)\n"
|
| 704 |
+
f"**h[0] = {h0}**\n\n"
|
| 705 |
+
f"**For n = 1:**\n"
|
| 706 |
+
f"h[1] {fmt(a1, 'h[0]')}h[0] {fmt(a2, 'h[-1]')}h[-1] = ... {fmt(b1, 'd[0]')}*delta[0] ...\n"
|
| 707 |
+
f"h[1] {fmt(a1, h0)}*({h0}) {fmt(a2, '0')}*(0) = {b0}*(0) + {b1}*(1) + {b2}*(0)\n"
|
| 708 |
+
f"h[1] = {b1} - {a1*h0}\n"
|
| 709 |
+
f"**h[1] = {h1}**\n\n"
|
| 710 |
+
|
| 711 |
+
f"**Step 4:** Find the Homogeneous Solution\n"
|
| 712 |
+
f"For n >= 3, the input is zero, and the equation is homogeneous:\n"
|
| 713 |
+
f"h[n] {fmt(a1, 'h[n-1]')}h[n-1] {fmt(a2, 'h[n-2]')}h[n-2] = 0\n\n"
|
| 714 |
+
f"We solve this by finding the roots of the characteristic equation: r**2 {fmt(a1, 'r')}r {fmt(a2, '')} = 0\n"
|
| 715 |
+
f"Using the quadratic formula, the roots are r1 = {r1_str}, r2 = {r2_str}.\n"
|
| 716 |
+
f"The general solution for n >= 0 is h[n] = C1*({r1_str})**n + C2*({r2_str})**n.\n\n"
|
| 717 |
+
|
| 718 |
+
f"**Step 5:** Use Initial Conditions to Find Coefficients\n"
|
| 719 |
+
f"We use h[0] and h[1] to create a system of two equations:\n"
|
| 720 |
+
f"1) For n=0: h[0] = C1 + C2 => {h0} = C1 + C2\n"
|
| 721 |
+
f"2) For n=1: h[1] = C1*({r1_str}) + C2*({r2_str}) => {h1} = {r1_str}*C1 + {r2_str}*C2\n\n"
|
| 722 |
+
f"Solving this system yields:\n"
|
| 723 |
+
f"**C1 = {C1_str}** and **C2 = {C2_str}**\n"
|
| 724 |
+
)
|
| 725 |
+
|
| 726 |
+
question = (
|
| 727 |
+
f"A causal LTI system is described by the difference equation:\n"
|
| 728 |
+
f"{equation_str}\n\n"
|
| 729 |
+
f"Find the impulse response h[n] of the system."
|
| 730 |
+
)
|
| 731 |
+
|
| 732 |
+
solution = (
|
| 733 |
+
f"**Given:**\n"
|
| 734 |
+
f"The system equation is {equation_str}\n\n"
|
| 735 |
+
|
| 736 |
+
f"**Step 1:** Set Input to the Unit Impulse\n"
|
| 737 |
+
f"By definition, the impulse response h[n] is the output y[n] when the input x[n] is the unit impulse, delta[n].\n\n"
|
| 738 |
+
|
| 739 |
+
f"**Step 2:** Substitute h[n] and delta[n] into the Equation\n"
|
| 740 |
+
f"Replacing y[n] with h[n] and x[n] with delta[n], we get:\n"
|
| 741 |
+
f"{equation_str.replace('y', 'h').replace('x', 'delta')}\n\n"
|
| 742 |
+
|
| 743 |
+
f"{solution_steps}\n"
|
| 744 |
+
|
| 745 |
+
f"**Answer:**\n"
|
| 746 |
+
f"Substituting the coefficients back into the general form, the impulse response is:\n"
|
| 747 |
+
f"h[n] = {final_h_n}"
|
| 748 |
+
)
|
| 749 |
+
|
| 750 |
+
return question, solution
|
| 751 |
+
|
| 752 |
+
|
| 753 |
+
def main():
|
| 754 |
+
"""
|
| 755 |
+
Generate numerous instances of each discrete time signals template
|
| 756 |
+
with different random seeds and write the results to a JSONL file.
|
| 757 |
+
"""
|
| 758 |
+
import json
|
| 759 |
+
import os
|
| 760 |
+
|
| 761 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 762 |
+
output_file = "testset/electrical_engineering/signals_and_systems/discrete_time_signals.jsonl"
|
| 763 |
+
|
| 764 |
+
# Create the directory if it doesn't exist
|
| 765 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 766 |
+
|
| 767 |
+
# List of template functions with their ID and level
|
| 768 |
+
templates = [
|
| 769 |
+
(template_signal_operations, "signal_operations", "Easy"),
|
| 770 |
+
(template_system_properties_memory_causality, "system_properties_memory_causality", "Easy"),
|
| 771 |
+
(template_finite_convolution, "finite_convolution", "Intermediate"),
|
| 772 |
+
(template_system_property_linearity, "system_property_linearity", "Intermediate"),
|
| 773 |
+
(template_impulse_response_from_lccde, "impulse_response_from_lccde", "Advanced"),
|
| 774 |
+
]
|
| 775 |
+
|
| 776 |
+
# List to store all generated problems
|
| 777 |
+
all_problems = []
|
| 778 |
+
|
| 779 |
+
# Generate problems for each template
|
| 780 |
+
for template_func, id_name, level in templates:
|
| 781 |
+
for _ in range(50):
|
| 782 |
+
# Generate a unique seed for each problem
|
| 783 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 784 |
+
random.seed(seed)
|
| 785 |
+
|
| 786 |
+
# Generate the problem and solution
|
| 787 |
+
question, solution = template_func()
|
| 788 |
+
|
| 789 |
+
# Create a JSON entry
|
| 790 |
+
problem_entry = {
|
| 791 |
+
"seed": seed,
|
| 792 |
+
"branch": "electrical_engineering",
|
| 793 |
+
"domain": "signals_and_systems",
|
| 794 |
+
"area": "discrete_time_signals",
|
| 795 |
+
"id": id_name,
|
| 796 |
+
"level": level,
|
| 797 |
+
"question": question,
|
| 798 |
+
"solution": solution
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
# Add to the list of problems
|
| 802 |
+
all_problems.append(problem_entry)
|
| 803 |
+
|
| 804 |
+
# Shuffle the problems to mix templates and levels
|
| 805 |
+
random.shuffle(all_problems)
|
| 806 |
+
|
| 807 |
+
# Write all problems to a .jsonl file
|
| 808 |
+
with open(output_file, "w") as file:
|
| 809 |
+
for problem in all_problems:
|
| 810 |
+
file.write(json.dumps(problem))
|
| 811 |
+
file.write("\n")
|
| 812 |
+
|
| 813 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 814 |
+
|
| 815 |
+
|
| 816 |
+
if __name__ == "__main__":
|
| 817 |
+
main()
|
data/templates/branches/mechanical_engineering/constants.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# E is the Modulus of Elasticity.
|
| 2 |
+
MATERIAL_PROPERTIES = {
|
| 3 |
+
# Metals (Common)
|
| 4 |
+
'Steel': {'E_GPa': 200, 'E_ksi': 29000, 'nu': 0.30},
|
| 5 |
+
'Stainless Steel': {'E_GPa': 190, 'E_ksi': 27500, 'nu': 0.30}, # 304 Stainless
|
| 6 |
+
'Aluminum': {'E_GPa': 69, 'E_ksi': 10000, 'nu': 0.33}, # General purpose 1100, 3003 Al
|
| 7 |
+
'Aluminum 6061-T6': {'E_GPa': 68.9, 'E_ksi': 10000, 'nu': 0.33}, # A common specific alloy
|
| 8 |
+
'Copper': {'E_GPa': 117, 'E_ksi': 17000, 'nu': 0.34},
|
| 9 |
+
'Brass': {'E_GPa': 102, 'E_ksi': 14800, 'nu': 0.34}, # Yellow Brass (CuZn37)
|
| 10 |
+
'Bronze': {'E_GPa': 110, 'E_ksi': 16000, 'nu': 0.34}, # Phosphor Bronze
|
| 11 |
+
'Titanium': {'E_GPa': 116, 'E_ksi': 16800, 'nu': 0.34}, # Commercially Pure
|
| 12 |
+
'Titanium Alloy (6Al-4V)': {'E_GPa': 114, 'E_ksi': 16500, 'nu': 0.34},
|
| 13 |
+
'Magnesium': {'E_GPa': 45, 'E_ksi': 6500, 'nu': 0.29}, # AZ31B alloy
|
| 14 |
+
'Tungsten': {'E_GPa': 411, 'E_ksi': 59600, 'nu': 0.28},
|
| 15 |
+
'Cast Iron': {'E_GPa': 170, 'E_ksi': 24600, 'nu': 0.26}, # Gray Cast Iron
|
| 16 |
+
'Nickel': {'E_GPa': 207, 'E_ksi': 30000, 'nu': 0.31},
|
| 17 |
+
'Lead': {'E_GPa': 16, 'E_ksi': 2300, 'nu': 0.44}, # Added a common soft metal
|
| 18 |
+
|
| 19 |
+
# Polymers/Plastics
|
| 20 |
+
'Nylon': {'E_GPa': 2.1, 'E_ksi': 300, 'nu': 0.39}, # Nylon 6/6
|
| 21 |
+
'Polycarbonate': {'E_GPa': 2.3, 'E_ksi': 334, 'nu': 0.38},
|
| 22 |
+
'ABS': {'E_GPa': 2.0, 'E_ksi': 290, 'nu': 0.35},
|
| 23 |
+
'PVC (rigid)': {'E_GPa': 3.0, 'E_ksi': 435, 'nu': 0.38},
|
| 24 |
+
'PTFE (Teflon)': {'E_GPa': 0.5, 'E_ksi': 72, 'nu': 0.46},
|
| 25 |
+
'Polyethylene (HDPE)': {'E_GPa': 1.1, 'E_ksi': 160, 'nu': 0.42},
|
| 26 |
+
'Epoxy': {'E_GPa': 3.0, 'E_ksi': 435, 'nu': 0.38}, # Unreinforced epoxy resin
|
| 27 |
+
'Natural Rubber': {'E_GPa': 0.0015, 'E_ksi': 0.22, 'nu': 0.4999}, # ~0.5 (nearly incompressible)
|
| 28 |
+
|
| 29 |
+
# Composites & Other
|
| 30 |
+
'Carbon Fiber Reinforced Polymer (CFRP)': {'E_GPa': 150, 'E_ksi': 21750, 'nu': 0.30}, # Direction-dependent, this is a typical in-plane value
|
| 31 |
+
'Fiberglass (GFRP)': {'E_GPa': 45, 'E_ksi': 6500, 'nu': 0.25}, # Direction-dependent, typical in-plane value
|
| 32 |
+
'Concrete': {'E_GPa': 30, 'E_ksi': 4350, 'nu': 0.15}, # Highly variable, common approx.
|
| 33 |
+
'Glass (Borosilicate)': {'E_GPa': 70, 'E_ksi': 10150, 'nu': 0.20},
|
| 34 |
+
'Ceramic (Alumina Al2O3)': {'E_GPa': 370, 'E_ksi': 53700, 'nu': 0.22},
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# Typical Shear Modulus (G) Values for Engineering Materials (GPa)
|
| 39 |
+
# Note: Values are representative and can vary with alloy composition and heat treatment.
|
| 40 |
+
SHEAR_MODULUS_VALUES = {
|
| 41 |
+
# Metals (Common Alloys)
|
| 42 |
+
"Steel (A36)": 77.2,
|
| 43 |
+
"Stainless Steel (304)": 77.0,
|
| 44 |
+
"Aluminum 6061-T6": 26.0,
|
| 45 |
+
"Aluminum 2024-T4": 28.0,
|
| 46 |
+
"Brass (C36000)": 39.0,
|
| 47 |
+
"Copper (CDA 110)": 44.7,
|
| 48 |
+
"Bronze (Phosphor 510)": 41.4,
|
| 49 |
+
"Titanium Alloy (Ti-6Al-4V)": 41.4,
|
| 50 |
+
"Magnesium Alloy (AZ31B)": 16.5,
|
| 51 |
+
"Tungsten": 160.0,
|
| 52 |
+
"Molybdenum": 118.0,
|
| 53 |
+
"Lead": 5.9,
|
| 54 |
+
|
| 55 |
+
# Other Materials
|
| 56 |
+
"Gray Cast Iron": 44.0, # Note: Anisotropic property, value is an approximation.
|
| 57 |
+
"Nylon 6/6": 1.1, # Polymers have much lower moduli
|
| 58 |
+
"Polycarbonate": 0.9,
|
| 59 |
+
"Concrete": 22.0, # Highly variable, this is a common estimate for calculation
|
| 60 |
+
"Glass": 26.2,
|
| 61 |
+
|
| 62 |
+
# Composites (Highly variable based on fiber orientation and volume fraction)
|
| 63 |
+
# Value given is an approximate in-plane shear modulus.
|
| 64 |
+
"Carbon Fiber Epoxy (Unidirectional, in-plane)": 5.0,
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
# Standard atmospheric pressure in kPa
|
| 68 |
+
ATMOSPHERIC_PRESSURE_KPA = 101.325
|
| 69 |
+
|
| 70 |
+
# Standard acceleration due to gravity in m/s^2
|
| 71 |
+
GRAVITY = 9.81
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# Densities of common fluids in kg/m^3 at 20°C and 1 atm, unless specified.
|
| 75 |
+
FLUID_DENSITIES = {
|
| 76 |
+
# Water-based
|
| 77 |
+
"Fresh Water": 998, # More precise value for 20°C
|
| 78 |
+
"Sea Water": 1025,
|
| 79 |
+
"Brine (20% NaCl)": 1150, # Common industrial solution
|
| 80 |
+
|
| 81 |
+
# Oils and Hydrocarbons
|
| 82 |
+
"SAE 10W Oil": 870,
|
| 83 |
+
"SAE 30 Oil": 917,
|
| 84 |
+
"Crude Oil": 870, # Typical average value
|
| 85 |
+
"Kerosene": 810,
|
| 86 |
+
"Diesel Fuel": 850,
|
| 87 |
+
"Gasoline": 726,
|
| 88 |
+
"Engine Oil": 888,
|
| 89 |
+
"Hydraulic Oil": 850, # Typical average value
|
| 90 |
+
|
| 91 |
+
# Alcohols and Solvents
|
| 92 |
+
"Ethyl Alcohol (Ethanol)": 789,
|
| 93 |
+
"Methyl Alcohol (Methanol)": 791,
|
| 94 |
+
"Isopropyl Alcohol (IPA)": 786,
|
| 95 |
+
"Acetone": 791,
|
| 96 |
+
"Turpentine": 870,
|
| 97 |
+
"Benzene": 876,
|
| 98 |
+
"Toluene": 867,
|
| 99 |
+
"Xylene": 870,
|
| 100 |
+
|
| 101 |
+
# Cryogens & Liquefied Gases
|
| 102 |
+
"Liquid Nitrogen (at -196°C)": 807,
|
| 103 |
+
"Liquid Oxygen (at -183°C)": 1141,
|
| 104 |
+
"Liquid Hydrogen (at -253°C)": 71,
|
| 105 |
+
"Liquid Propane": 493, # At 25°C, under pressure
|
| 106 |
+
|
| 107 |
+
# High-Density Liquids
|
| 108 |
+
"Mercury": 13550,
|
| 109 |
+
"Glycerin": 1260,
|
| 110 |
+
"Chloroform": 1489,
|
| 111 |
+
"Carbon Tetrachloride": 1594,
|
| 112 |
+
"Bromine": 3120,
|
| 113 |
+
"Sulfuric Acid (98%)": 1830,
|
| 114 |
+
"Hydrochloric Acid (37%)": 1190, # Also known as Muriatic Acid
|
| 115 |
+
"Nitric Acid (68%)": 1350,
|
| 116 |
+
|
| 117 |
+
# Common Liquids
|
| 118 |
+
"Milk": 1035,
|
| 119 |
+
"Whole Blood": 1060, # Typical value for human blood
|
| 120 |
+
"Ethylene Glycol (Antifreeze)": 1113,
|
| 121 |
+
"Corn Syrup": 1380,
|
| 122 |
+
"Olive Oil": 910,
|
| 123 |
+
"Vegetable Oil": 920,
|
| 124 |
+
"R-134a Refrigerant (Saturated Liquid at 25°C)": 1206,
|
| 125 |
+
|
| 126 |
+
# Gases (at 1 atm, for comparison - though not for hydrostatic problems!)
|
| 127 |
+
"Air (at 20°C)": 1.2,
|
| 128 |
+
"Helium (at 20°C)": 0.166,
|
| 129 |
+
"Carbon Dioxide (at 20°C)": 1.84
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
# Densities of various solid materials in kg/m^3
|
| 133 |
+
MATERIAL_DENSITIES = {
|
| 134 |
+
# Woods & Natural Materials (generally float in water)
|
| 135 |
+
"Pine Wood": 500,
|
| 136 |
+
"Oak Wood": 750,
|
| 137 |
+
"Cork": 240,
|
| 138 |
+
"Teak Wood": 630,
|
| 139 |
+
"Maple Wood": 740,
|
| 140 |
+
"Ebony Wood": 1200, # Sinks in water
|
| 141 |
+
"Bamboo": 300,
|
| 142 |
+
"Rubber (Natural)": 950,
|
| 143 |
+
|
| 144 |
+
# Plastics & Synthetics (range from floating to sinking)
|
| 145 |
+
"Polypropylene (PP)": 900,
|
| 146 |
+
"Polyethylene (LDPE)": 920,
|
| 147 |
+
"Polyethylene (HDPE)": 950,
|
| 148 |
+
"Nylon": 1150,
|
| 149 |
+
"Polystyrene (PS)": 1050,
|
| 150 |
+
"PVC (Rigid)": 1380,
|
| 151 |
+
"PTFE (Teflon)": 2200,
|
| 152 |
+
"Acrylic (Plexiglas)": 1180,
|
| 153 |
+
|
| 154 |
+
# Biological Materials
|
| 155 |
+
"Human Body (avg.)": 985, # Slightly less than water; most people float
|
| 156 |
+
"Apple": 800,
|
| 157 |
+
"Bone": 1850,
|
| 158 |
+
|
| 159 |
+
# Lightweight & Porous Materials
|
| 160 |
+
"Aerogel": 3,
|
| 161 |
+
"Pumice": 700, # The only rock that floats!
|
| 162 |
+
"Brick": 1700,
|
| 163 |
+
"Concrete (avg.)": 2400,
|
| 164 |
+
|
| 165 |
+
# Common Metals & Alloys (will sink in water, float in mercury)
|
| 166 |
+
"Aluminum": 2710,
|
| 167 |
+
"Titanium": 4500,
|
| 168 |
+
"Zinc": 7140,
|
| 169 |
+
"Tin": 7280,
|
| 170 |
+
"Iron (Wrought)": 7750,
|
| 171 |
+
"Steel (Carbon)": 7850,
|
| 172 |
+
"Brass": 8600,
|
| 173 |
+
"Copper": 8940,
|
| 174 |
+
"Nickel": 8900,
|
| 175 |
+
"Silver": 10500,
|
| 176 |
+
"Lead": 11340,
|
| 177 |
+
"Uranium": 19100,
|
| 178 |
+
"Gold": 19300,
|
| 179 |
+
"Tungsten": 19600,
|
| 180 |
+
"Platinum": 21450,
|
| 181 |
+
"Osmium": 22590, # The densest naturally occurring element
|
| 182 |
+
|
| 183 |
+
# Other Materials
|
| 184 |
+
"Ice (0°C)": 917,
|
| 185 |
+
"Wax (Paraffin)": 900,
|
| 186 |
+
"Glass (Window)": 2500,
|
| 187 |
+
"Glass (Crystal)": 2900,
|
| 188 |
+
"Graphite": 2100,
|
| 189 |
+
"Diamond": 3500,
|
| 190 |
+
"Quartz": 2650,
|
| 191 |
+
"Granite": 2700,
|
| 192 |
+
"Marble": 2700,
|
| 193 |
+
"Sandstone": 2300,
|
| 194 |
+
"Limestone": 2500,
|
| 195 |
+
"Cork Board": 240,
|
| 196 |
+
"Paper": 800,
|
| 197 |
+
"Leather (Dry)": 860,
|
| 198 |
+
"Chalk": 2500,
|
| 199 |
+
"Asphalt": 1100,
|
| 200 |
+
"Coal (Anthracite)": 1500,
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
OBJECT_SHAPES = [
|
| 204 |
+
"sphere", "cube", "irregular block", "cylinder", "anchor",
|
| 205 |
+
"rectangular prism", "cone", "pyramid", "torpedo", "submarine hull",
|
| 206 |
+
"ball", "ingot", "pipe section", "bead", "marble",
|
| 207 |
+
"statue", "metal ball", "wooden block", "concrete piling", "boat hull",
|
| 208 |
+
"metal rod", "canister", "storage tank", "buoy", "weight",
|
| 209 |
+
"metal box", "stone", "boulder", "metal cylinder", "plastic container"
|
| 210 |
+
]
|
| 211 |
+
|
| 212 |
+
OBJECT_MATERIALS = [
|
| 213 |
+
"steel", "aluminum", "copper", "concrete", "plastic",
|
| 214 |
+
"wood (oak)", "wood (pine)", "glass", "gold", "lead",
|
| 215 |
+
"brass", "bronze", "iron", "titanium", "rubber",
|
| 216 |
+
"cork", "foam", "ice", "wax", "ceramic",
|
| 217 |
+
"polyethylene", "PVC", "fiberglass", "granite", "marble",
|
| 218 |
+
"ebony", "balsa wood", "uranium", "magnesium", "tungsten"
|
| 219 |
+
]
|
| 220 |
+
|
| 221 |
+
# Densities of common fluids in kg/m^3
|
| 222 |
+
# Lighter fluids suitable for the pipe
|
| 223 |
+
PIPE_FLUIDS = {
|
| 224 |
+
# Gases (Very Light)
|
| 225 |
+
"Air": 1.225,
|
| 226 |
+
"Helium": 0.1786,
|
| 227 |
+
"Hydrogen": 0.0899,
|
| 228 |
+
"Natural Gas (Methane)": 0.717,
|
| 229 |
+
"Carbon Dioxide": 1.98,
|
| 230 |
+
|
| 231 |
+
# Light Hydrocarbons & Fuels
|
| 232 |
+
"Gasoline": 726,
|
| 233 |
+
"Kerosene": 810,
|
| 234 |
+
"Diesel Fuel": 850,
|
| 235 |
+
"Jet Fuel (JP-4)": 770,
|
| 236 |
+
"Ethanol": 789,
|
| 237 |
+
"Methanol": 791,
|
| 238 |
+
"Isopropyl Alcohol": 786,
|
| 239 |
+
|
| 240 |
+
# Oils & Lubricants
|
| 241 |
+
"SAE 10 Oil": 870,
|
| 242 |
+
"SAE 20 Oil": 880,
|
| 243 |
+
"SAE 30 Oil": 917,
|
| 244 |
+
"SAE 40 Oil": 945,
|
| 245 |
+
"SAE 50 Oil": 960,
|
| 246 |
+
"Crude Oil (Light)": 825,
|
| 247 |
+
"Crude Oil (Heavy)": 950,
|
| 248 |
+
"Engine Oil": 888,
|
| 249 |
+
"Hydraulic Oil": 860,
|
| 250 |
+
|
| 251 |
+
# Common Liquids
|
| 252 |
+
"Water": 1000,
|
| 253 |
+
"Sea Water": 1025,
|
| 254 |
+
"Milk": 1030,
|
| 255 |
+
"Ethylene Glycol": 1110,
|
| 256 |
+
"Antifreeze (50/50)": 1065,
|
| 257 |
+
|
| 258 |
+
# Cryogenic Fluids
|
| 259 |
+
"Liquid Nitrogen": 810,
|
| 260 |
+
"Liquid Oxygen": 1141,
|
| 261 |
+
|
| 262 |
+
# Chemical Solvents
|
| 263 |
+
"Acetone": 784,
|
| 264 |
+
"Toluene": 867,
|
| 265 |
+
"Benzene": 876,
|
| 266 |
+
"Hexane": 655
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
# Denser fluids suitable for the manometer
|
| 270 |
+
MANOMETER_FLUIDS = {
|
| 271 |
+
# Standard Manometer Fluids
|
| 272 |
+
"Mercury": 13550,
|
| 273 |
+
"Water": 1000, # For gas measurements
|
| 274 |
+
"Sea Water": 1025,
|
| 275 |
+
|
| 276 |
+
# Heavy Oils & Lubricants
|
| 277 |
+
"SAE 50 Oil": 960,
|
| 278 |
+
"SAE 90 Gear Oil": 920,
|
| 279 |
+
|
| 280 |
+
# Organic Liquids
|
| 281 |
+
"Carbon Tetrachloride": 1590,
|
| 282 |
+
"Chloroform": 1480,
|
| 283 |
+
"Bromine": 3120,
|
| 284 |
+
"Tetrabromoethane": 2960,
|
| 285 |
+
|
| 286 |
+
# Inorganic Solutions
|
| 287 |
+
"Calcium Chloride Solution (40%)": 1390,
|
| 288 |
+
"Zinc Chloride Solution (50%)": 1520,
|
| 289 |
+
"Sodium Polysulfide": 1650,
|
| 290 |
+
|
| 291 |
+
# Specialty Fluids
|
| 292 |
+
"Glycerin": 1260,
|
| 293 |
+
"Diiodomethane": 3325,
|
| 294 |
+
"Acetylene Tetrabromide": 2960,
|
| 295 |
+
|
| 296 |
+
# Molten Metals (for high-temperature applications)
|
| 297 |
+
"Gallium": 6095,
|
| 298 |
+
"Tin": 6980,
|
| 299 |
+
"Zinc": 6570,
|
| 300 |
+
|
| 301 |
+
# Very Heavy Fluids
|
| 302 |
+
"Tellurium Mercury": 8100,
|
| 303 |
+
"Tungsten Hexafluoride": 12900
|
| 304 |
+
}
|
data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_kinematics.py
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_fluid_particle_acceleration():
|
| 7 |
+
"""
|
| 8 |
+
Fluid Kinematics: Fluid Particle Acceleration in a 2D Field
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template generates a problem that tests the ability to calculate the
|
| 12 |
+
acceleration of a fluid particle at a specific point and time. It requires
|
| 13 |
+
applying the material derivative to a given 2D unsteady velocity field.
|
| 14 |
+
The problem distinguishes between the local (time-based) and convective
|
| 15 |
+
(space-based) components of acceleration.
|
| 16 |
+
|
| 17 |
+
Core Equations:
|
| 18 |
+
Velocity Field: V(x, y, t) = u(x, y, t)i + v(x, y, t)j
|
| 19 |
+
Acceleration (ax) = du/dt + u*(du/dx) + v*(du/dy)
|
| 20 |
+
Acceleration (ay) = dv/dt + u*(dv/dx) + v*(dv/dy)
|
| 21 |
+
Total Acceleration (a) = sqrt(ax^2 + ay^2)
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
tuple: A tuple containing:
|
| 25 |
+
- str: A question asking for the acceleration components and magnitude.
|
| 26 |
+
- str: A step-by-step solution to the problem.
|
| 27 |
+
"""
|
| 28 |
+
# 1. Parameterize the inputs with random values for high diversity
|
| 29 |
+
|
| 30 |
+
# Coefficients for the velocity field polynomials
|
| 31 |
+
# u = A*x*t + B*y**2
|
| 32 |
+
# v = C*x*y + D*t**2
|
| 33 |
+
A = random.randint(1, 10)
|
| 34 |
+
B = random.randint(1, 10)
|
| 35 |
+
C = random.randint(1, 10)
|
| 36 |
+
D = random.randint(1, 10)
|
| 37 |
+
|
| 38 |
+
# Specific point (x, y) and time (t) for evaluation
|
| 39 |
+
x_point = round(random.uniform(0.5, 5.0), 1)
|
| 40 |
+
y_point = round(random.uniform(0.5, 5.0), 1)
|
| 41 |
+
t_point = round(random.uniform(0.1, 3.0), 1)
|
| 42 |
+
|
| 43 |
+
# Standardize precision for all calculations and outputs
|
| 44 |
+
precision = 3
|
| 45 |
+
|
| 46 |
+
# 2. Perform the core calculations for the solution
|
| 47 |
+
|
| 48 |
+
# Step A: Evaluate velocity components u and v at the given point and time
|
| 49 |
+
u_val = A * x_point * t_point + B * y_point**2
|
| 50 |
+
v_val = C * x_point * y_point + D * t_point**2
|
| 51 |
+
|
| 52 |
+
# Step B: Calculate all necessary partial derivatives
|
| 53 |
+
# For u(x, y, t) = A*x*t + B*y^2
|
| 54 |
+
du_dt = A * x_point
|
| 55 |
+
du_dx = A * t_point
|
| 56 |
+
du_dy = 2 * B * y_point
|
| 57 |
+
|
| 58 |
+
# For v(x, y, t) = C*x*y + D*t^2
|
| 59 |
+
dv_dt = 2 * D * t_point
|
| 60 |
+
dv_dx = C * y_point
|
| 61 |
+
dv_dy = C * x_point
|
| 62 |
+
|
| 63 |
+
# Step C: Calculate the acceleration components (ax and ay)
|
| 64 |
+
# ax = (local acceleration in x) + (convective acceleration in x)
|
| 65 |
+
local_ax = du_dt
|
| 66 |
+
convective_ax = u_val * du_dx + v_val * du_dy
|
| 67 |
+
ax = local_ax + convective_ax
|
| 68 |
+
|
| 69 |
+
# ay = (local acceleration in y) + (convective acceleration in y)
|
| 70 |
+
local_ay = dv_dt
|
| 71 |
+
convective_ay = u_val * dv_dx + v_val * dv_dy
|
| 72 |
+
ay = local_ay + convective_ay
|
| 73 |
+
|
| 74 |
+
# Step D: Calculate the magnitude of the total acceleration
|
| 75 |
+
a_magnitude = math.sqrt(ax**2 + ay**2)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
# 3. Generate the question and solution strings
|
| 79 |
+
|
| 80 |
+
question = (
|
| 81 |
+
f"The velocity field of a fluid is given by the equations:\n"
|
| 82 |
+
f"u = {A}xt + {B}y^2\n"
|
| 83 |
+
f"v = {C}xy + {D}t^2\n"
|
| 84 |
+
f"where u and v are in m/s, and x and y are in meters, and t is in seconds.\n\n"
|
| 85 |
+
f"Determine the acceleration components (ax and ay) and the magnitude of the "
|
| 86 |
+
f"acceleration for a fluid particle at the point ({x_point}, {y_point}) m at time t = {t_point} s."
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
solution = (
|
| 90 |
+
f"**Given:**\n"
|
| 91 |
+
f"Velocity component u = {A}xt + {B}y^2\n"
|
| 92 |
+
f"Velocity component v = {C}xy + {D}t^2\n"
|
| 93 |
+
f"Point of interest: (x, y) = ({x_point}, {y_point}) m\n"
|
| 94 |
+
f"Time of interest: t = {t_point} s\n\n"
|
| 95 |
+
|
| 96 |
+
f"**Step 1:** Calculate the acceleration component in the x-direction (ax).\n"
|
| 97 |
+
f"The formula is: ax = du/dt + u*(du/dx) + v*(du/dy)\n\n"
|
| 98 |
+
|
| 99 |
+
f"First, find the required partial derivatives of u:\n"
|
| 100 |
+
f"du/dt = d/dt({A}xt + {B}y^2) = {A}x -> At ({x_point}, {t_point}), du/dt = {A} * {x_point} = {round(du_dt, precision)}\n"
|
| 101 |
+
f"du/dx = d/dx({A}xt + {B}y^2) = {A}t -> At ({x_point}, {t_point}), du/dx = {A} * {t_point} = {round(du_dx, precision)}\n"
|
| 102 |
+
f"du/dy = d/dy({A}xt + {B}y^2) = {2*B}y -> At ({y_point}), du/dy = {2*B} * {y_point} = {round(du_dy, precision)}\n\n"
|
| 103 |
+
|
| 104 |
+
f"Next, evaluate u and v at the specified point and time:\n"
|
| 105 |
+
f"u = {A}({x_point})({t_point}) + {B}({y_point})^2 = {round(u_val, precision)} m/s\n"
|
| 106 |
+
f"v = {C}({x_point})({y_point}) + {D}({t_point})^2 = {round(v_val, precision)} m/s\n\n"
|
| 107 |
+
|
| 108 |
+
f"Now, substitute these values into the acceleration formula for ax:\n"
|
| 109 |
+
f"ax = (du/dt) + u*(du/dx) + v*(du/dy)\n"
|
| 110 |
+
f"ax = {round(du_dt, precision)} + ({round(u_val, precision)})*({round(du_dx, precision)}) + ({round(v_val, precision)})*({round(du_dy, precision)})\n"
|
| 111 |
+
f"ax = {round(local_ax, precision)} + {round(convective_ax, precision)} = {round(ax, precision)} m/s^2\n\n"
|
| 112 |
+
|
| 113 |
+
f"**Step 2:** Calculate the acceleration component in the y-direction (ay).\n"
|
| 114 |
+
f"The formula is: ay = dv/dt + u*(dv/dx) + v*(dv/dy)\n\n"
|
| 115 |
+
|
| 116 |
+
f"First, find the required partial derivatives of v:\n"
|
| 117 |
+
f"dv/dt = d/dt({C}xy + {D}t^2) = {2*D}t -> At ({t_point}), dv/dt = {2*D} * {t_point} = {round(dv_dt, precision)}\n"
|
| 118 |
+
f"dv/dx = d/dx({C}xy + {D}t^2) = {C}y -> At ({y_point}), dv/dx = {C} * {y_point} = {round(dv_dx, precision)}\n"
|
| 119 |
+
f"dv/dy = d/dy({C}xy + {D}t^2) = {C}x -> At ({x_point}), dv/dy = {C} * {x_point} = {round(dv_dy, precision)}\n\n"
|
| 120 |
+
|
| 121 |
+
f"Using the already calculated u and v values:\n"
|
| 122 |
+
f"ay = (dv/dt) + u*(dv/dx) + v*(dv/dy)\n"
|
| 123 |
+
f"ay = {round(dv_dt, precision)} + ({round(u_val, precision)})*({round(dv_dx, precision)}) + ({round(v_val, precision)})*({round(dv_dy, precision)})\n"
|
| 124 |
+
f"ay = {round(local_ay, precision)} + {round(convective_ay, precision)} = {round(ay, precision)} m/s^2\n\n"
|
| 125 |
+
|
| 126 |
+
f"**Step 3:** Calculate the magnitude of the total acceleration.\n"
|
| 127 |
+
f"The formula is: a = sqrt(ax^2 + ay^2)\n"
|
| 128 |
+
f"a = sqrt(({round(ax, precision)})^2 + ({round(ay, precision)})^2)\n"
|
| 129 |
+
f"a = {round(a_magnitude, precision)} m/s^2\n\n"
|
| 130 |
+
|
| 131 |
+
f"**Answer:**\n"
|
| 132 |
+
f"The acceleration components are ax = {round(ax, precision)} m/s^2 and ay = {round(ay, precision)} m/s^2.\n"
|
| 133 |
+
f"The magnitude of the acceleration is {round(a_magnitude, precision)} m/s^2."
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
return question, solution
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
# Template 2 (Intermediate)
|
| 140 |
+
def template_volumetric_flow_rate():
|
| 141 |
+
"""
|
| 142 |
+
Fluid Kinematics: Volumetric Flow Rate from a Velocity Profile
|
| 143 |
+
|
| 144 |
+
Scenario:
|
| 145 |
+
This template assesses the ability to calculate the volumetric flow rate (Q)
|
| 146 |
+
by integrating a given velocity profile over a cross-sectional area. It also
|
| 147 |
+
calculates the average velocity. The problem randomly selects between flow
|
| 148 |
+
in a circular pipe (parabolic profile) and flow in a rectangular channel
|
| 149 |
+
(linear profile).
|
| 150 |
+
|
| 151 |
+
Core Equations:
|
| 152 |
+
Volumetric Flow Rate: Q = integral(u dA)
|
| 153 |
+
Average Velocity: V_avg = Q / A
|
| 154 |
+
Pipe Profile: u(r) = U_max * (1 - (r/R)^2), dA = 2*pi*r dr
|
| 155 |
+
Channel Profile: u(y) = U_max * (y/H), dA = W dy
|
| 156 |
+
|
| 157 |
+
Returns:
|
| 158 |
+
tuple: A tuple containing:
|
| 159 |
+
- str: A question asking for the flow rate and average velocity.
|
| 160 |
+
- str: A step-by-step solution to the problem.
|
| 161 |
+
"""
|
| 162 |
+
# 1. Parameterize the inputs with random values
|
| 163 |
+
geometry = random.choice(['pipe', 'channel'])
|
| 164 |
+
u_max = round(random.uniform(0.5, 10.0), 2)
|
| 165 |
+
precision = 4
|
| 166 |
+
|
| 167 |
+
# 2. Perform calculations and generate strings based on the chosen geometry
|
| 168 |
+
if geometry == 'pipe':
|
| 169 |
+
# --- Pipe-specific parameters ---
|
| 170 |
+
diameter_mm = random.randint(20, 200)
|
| 171 |
+
radius_m = diameter_mm / 2000.0
|
| 172 |
+
|
| 173 |
+
# --- Core calculations for the pipe ---
|
| 174 |
+
area = math.pi * radius_m**2
|
| 175 |
+
# For a parabolic profile in a pipe, the exact integral yields Q = (1/2) * U_max * A
|
| 176 |
+
flow_rate = 0.5 * u_max * area
|
| 177 |
+
avg_velocity = u_max / 2.0
|
| 178 |
+
|
| 179 |
+
# --- Generate question and solution strings for the pipe ---
|
| 180 |
+
question = (
|
| 181 |
+
f"The velocity profile for a fluid flowing through a circular pipe with a diameter of {diameter_mm} mm "
|
| 182 |
+
f"is given by u(r) = U_max * (1 - (r/R)^2), where R is the radius of the pipe and the maximum "
|
| 183 |
+
f"velocity U_max is {u_max} m/s at the centerline (r=0).\n\n"
|
| 184 |
+
f"Determine the volumetric flow rate (Q) and the average velocity (V_avg) of the fluid."
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
solution = (
|
| 188 |
+
f"**Given:**\n"
|
| 189 |
+
f"Geometry: Circular Pipe\n"
|
| 190 |
+
f"Diameter (D) = {diameter_mm} mm\n"
|
| 191 |
+
f"Maximum Velocity (U_max) = {u_max} m/s\n"
|
| 192 |
+
f"Velocity Profile: u(r) = U_max * (1 - (r/R)^2)\n\n"
|
| 193 |
+
|
| 194 |
+
f"**Step 1:** Determine pipe dimensions in meters.\n"
|
| 195 |
+
f"Radius (R) = D / 2 = {diameter_mm} / 2 = {diameter_mm/2.0} mm = {radius_m} m\n\n"
|
| 196 |
+
|
| 197 |
+
f"**Step 2:** Set up the integral for volumetric flow rate (Q).\n"
|
| 198 |
+
f"The formula is Q = integral(u dA) over the cross-sectional area.\n"
|
| 199 |
+
f"For a circular pipe, the differential area is a thin ring: dA = 2*pi*r dr.\n"
|
| 200 |
+
f"Q = integral from 0 to R of [U_max * (1 - (r/R)^2)] * (2*pi*r dr)\n"
|
| 201 |
+
f"Q = 2*pi*U_max * integral from 0 to R of (r - r^3/R^2) dr\n\n"
|
| 202 |
+
|
| 203 |
+
f"**Step 3:** Evaluate the integral.\n"
|
| 204 |
+
f"Q = 2*pi*U_max * [r^2/2 - r^4/(4*R^2)] from 0 to R\n"
|
| 205 |
+
f"Q = 2*pi*U_max * [(R^2/2 - R^4/(4*R^2)) - 0]\n"
|
| 206 |
+
f"Q = 2*pi*U_max * [R^2/4] = (pi*R^2*U_max) / 2\n\n"
|
| 207 |
+
|
| 208 |
+
f"**Step 4:** Substitute numerical values to find Q.\n"
|
| 209 |
+
f"Q = (pi * ({radius_m})^2 * {u_max}) / 2\n"
|
| 210 |
+
f"Q = {round(flow_rate, precision)} m^3/s\n\n"
|
| 211 |
+
|
| 212 |
+
f"**Step 5:** Calculate the average velocity (V_avg).\n"
|
| 213 |
+
f"First, calculate the cross-sectional area (A) = pi*R^2\n"
|
| 214 |
+
f"A = pi * ({radius_m})^2 = {round(area, precision)} m^2\n"
|
| 215 |
+
f"V_avg = Q / A = {round(flow_rate, precision)} / {round(area, precision)}\n"
|
| 216 |
+
f"Alternatively, for this profile, V_avg is known to be U_max / 2.\n"
|
| 217 |
+
f"V_avg = {u_max} / 2 = {round(avg_velocity, precision)} m/s\n\n"
|
| 218 |
+
|
| 219 |
+
f"**Answer:**\n"
|
| 220 |
+
f"The volumetric flow rate is {round(flow_rate, precision)} m^3/s, and the average velocity is {round(avg_velocity, precision)} m/s."
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
else: # geometry == 'channel'
|
| 224 |
+
# --- Channel-specific parameters ---
|
| 225 |
+
height_cm = random.randint(5, 50)
|
| 226 |
+
width_cm = random.randint(10, 100)
|
| 227 |
+
height_m = height_cm / 100.0
|
| 228 |
+
width_m = width_cm / 100.0
|
| 229 |
+
|
| 230 |
+
# --- Core calculations for the channel ---
|
| 231 |
+
area = width_m * height_m
|
| 232 |
+
# For a linear profile in a channel, the exact integral yields Q = (1/2) * U_max * A
|
| 233 |
+
flow_rate = 0.5 * u_max * area
|
| 234 |
+
avg_velocity = u_max / 2.0
|
| 235 |
+
|
| 236 |
+
# --- Generate question and solution strings for the channel ---
|
| 237 |
+
question = (
|
| 238 |
+
f"A fluid flows through a rectangular channel that is {width_cm} cm wide and {height_cm} cm high. "
|
| 239 |
+
f"The velocity profile is given by u(y) = U_max * (y/H), where H is the channel height and the "
|
| 240 |
+
f"maximum velocity U_max is {u_max} m/s at the top surface (y=H).\n\n"
|
| 241 |
+
f"Determine the volumetric flow rate (Q) and the average velocity (V_avg) of the fluid."
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
solution = (
|
| 245 |
+
f"**Given:**\n"
|
| 246 |
+
f"Geometry: Rectangular Channel\n"
|
| 247 |
+
f"Width (W) = {width_cm} cm\n"
|
| 248 |
+
f"Height (H) = {height_cm} cm\n"
|
| 249 |
+
f"Maximum Velocity (U_max) = {u_max} m/s\n"
|
| 250 |
+
f"Velocity Profile: u(y) = U_max * (y/H)\n\n"
|
| 251 |
+
|
| 252 |
+
f"**Step 1:** Determine channel dimensions in meters.\n"
|
| 253 |
+
f"Width (W) = {width_cm} cm = {width_m} m\n"
|
| 254 |
+
f"Height (H) = {height_cm} cm = {height_m} m\n\n"
|
| 255 |
+
|
| 256 |
+
f"**Step 2:** Set up the integral for volumetric flow rate (Q).\n"
|
| 257 |
+
f"The formula is Q = integral(u dA) over the cross-sectional area.\n"
|
| 258 |
+
f"For a rectangular channel, the differential area is a thin horizontal strip: dA = W dy.\n"
|
| 259 |
+
f"Q = integral from 0 to H of [U_max * (y/H)] * (W dy)\n"
|
| 260 |
+
f"Q = (W*U_max/H) * integral from 0 to H of (y) dy\n\n"
|
| 261 |
+
|
| 262 |
+
f"**Step 3:** Evaluate the integral.\n"
|
| 263 |
+
f"Q = (W*U_max/H) * [y^2/2] from 0 to H\n"
|
| 264 |
+
f"Q = (W*U_max/H) * [(H^2/2) - 0]\n"
|
| 265 |
+
f"Q = (W*U_max*H) / 2\n\n"
|
| 266 |
+
|
| 267 |
+
f"**Step 4:** Substitute numerical values to find Q.\n"
|
| 268 |
+
f"Q = ({width_m} * {u_max} * {height_m}) / 2\n"
|
| 269 |
+
f"Q = {round(flow_rate, precision)} m^3/s\n\n"
|
| 270 |
+
|
| 271 |
+
f"**Step 5:** Calculate the average velocity (V_avg).\n"
|
| 272 |
+
f"First, calculate the cross-sectional area (A) = W * H\n"
|
| 273 |
+
f"A = {width_m} * {height_m} = {round(area, precision)} m^2\n"
|
| 274 |
+
f"V_avg = Q / A = {round(flow_rate, precision)} / {round(area, precision)}\n"
|
| 275 |
+
f"Alternatively, for this profile, V_avg is known to be U_max / 2.\n"
|
| 276 |
+
f"V_avg = {u_max} / 2 = {round(avg_velocity, precision)} m/s\n\n"
|
| 277 |
+
|
| 278 |
+
f"**Answer:**\n"
|
| 279 |
+
f"The volumetric flow rate is {round(flow_rate, precision)} m^3/s, and the average velocity is {round(avg_velocity, precision)} m/s."
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
return question, solution
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
# Template 3 (Intermediate)
|
| 286 |
+
def template_vorticity_check():
|
| 287 |
+
"""
|
| 288 |
+
Fluid Kinematics: Vorticity and Rotational Flow Check
|
| 289 |
+
|
| 290 |
+
Scenario:
|
| 291 |
+
This template introduces the concept of fluid element rotation. It requires
|
| 292 |
+
calculating the curl of a given velocity field to find the vorticity vector.
|
| 293 |
+
This enhanced version uses more complex velocity fields where the vorticity
|
| 294 |
+
itself depends on the spatial coordinates, making the evaluation point critical
|
| 295 |
+
to the final answer. The problem randomly generates either a 2D or 3D field.
|
| 296 |
+
|
| 297 |
+
Core Equations:
|
| 298 |
+
Vorticity Vector: zeta = curl(V) = (dw/dy - dv/dz)i + (du/dz - dw/dx)j + (dv/dx - du/dy)k
|
| 299 |
+
Irrotational Flow: A flow is irrotational if the vorticity vector is the zero vector.
|
| 300 |
+
|
| 301 |
+
Returns:
|
| 302 |
+
tuple: A tuple containing:
|
| 303 |
+
- str: A question asking for the vorticity vector and a rotationality check.
|
| 304 |
+
- str: A step-by-step solution to the problem.
|
| 305 |
+
"""
|
| 306 |
+
# 1. Parameterize the inputs with random values
|
| 307 |
+
flow_type = random.choice(['2D', '3D'])
|
| 308 |
+
precision = 3
|
| 309 |
+
|
| 310 |
+
# Random coefficients for the velocity field polynomials.
|
| 311 |
+
# Allowing zeros increases variety, sometimes creating simpler or irrotational cases.
|
| 312 |
+
A = random.randint(-4, 4)
|
| 313 |
+
B = random.randint(-4, 4)
|
| 314 |
+
C = random.randint(-4, 4)
|
| 315 |
+
|
| 316 |
+
# Evaluation point
|
| 317 |
+
x_point = round(random.uniform(0.5, 3.0), 1)
|
| 318 |
+
y_point = round(random.uniform(0.5, 3.0), 1)
|
| 319 |
+
|
| 320 |
+
# 2. Perform calculations and generate strings based on the flow type
|
| 321 |
+
if flow_type == '2D':
|
| 322 |
+
# --- Define a more complex 2D velocity field ---
|
| 323 |
+
# u = A*x*y, v = B*x^2 + C*y^2
|
| 324 |
+
# This makes derivatives dependent on the point (x, y).
|
| 325 |
+
|
| 326 |
+
# --- Core calculations for 2D flow ---
|
| 327 |
+
du_dy_val = A * x_point
|
| 328 |
+
dv_dx_val = 2 * B * x_point
|
| 329 |
+
|
| 330 |
+
zeta_z = dv_dx_val - du_dy_val
|
| 331 |
+
is_irrotational = abs(zeta_z) < 1e-9 # Check for zero with floating point tolerance
|
| 332 |
+
|
| 333 |
+
# --- Generate question and solution strings for 2D flow ---
|
| 334 |
+
question = (
|
| 335 |
+
f"A 2D fluid flow is described by the velocity field:\n"
|
| 336 |
+
f"u = {A}xy\n"
|
| 337 |
+
f"v = {B}x^2 + {C}y^2\n"
|
| 338 |
+
f"where u and v are in m/s, and x and y are in meters.\n\n"
|
| 339 |
+
f"Calculate the vorticity at the point ({x_point}, {y_point}) m and "
|
| 340 |
+
f"determine if the flow is rotational or irrotational at that point."
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
solution = (
|
| 344 |
+
f"**Given:**\n"
|
| 345 |
+
f"Velocity component u = {A}xy\n"
|
| 346 |
+
f"Velocity component v = {B}x^2 + {C}y^2\n"
|
| 347 |
+
f"Point of interest: (x, y) = ({x_point}, {y_point}) m\n\n"
|
| 348 |
+
|
| 349 |
+
f"**Step 1:** State the formula for vorticity in a 2D flow.\n"
|
| 350 |
+
f"For a 2D flow in the x-y plane, the vorticity vector is (0, 0, zeta_z).\n"
|
| 351 |
+
f"The formula for the z-component is: zeta_z = (dv/dx - du/dy)\n\n"
|
| 352 |
+
|
| 353 |
+
f"**Step 2:** Calculate the necessary partial derivatives.\n"
|
| 354 |
+
f" du/dy = d/dy({A}xy) = {A}x\n"
|
| 355 |
+
f" dv/dx = d/dx({B}x^2 + {C}y^2) = {2*B}x\n\n"
|
| 356 |
+
|
| 357 |
+
f"**Step 3:** Evaluate the partial derivatives at the point ({x_point}, {y_point}).\n"
|
| 358 |
+
f" du/dy at x={x_point} -> {A}({x_point}) = {round(du_dy_val, precision)}\n"
|
| 359 |
+
f" dv/dx at x={x_point} -> {2*B}({x_point}) = {round(dv_dx_val, precision)}\n\n"
|
| 360 |
+
|
| 361 |
+
f"**Step 4:** Substitute the derivative values to find the vorticity component.\n"
|
| 362 |
+
f" zeta_z = ({round(dv_dx_val, precision)}) - ({round(du_dy_val, precision)}) = {round(zeta_z, precision)}\n"
|
| 363 |
+
f"The vorticity vector at this point is (0, 0, {round(zeta_z, precision)}) rad/s.\n\n"
|
| 364 |
+
|
| 365 |
+
f"**Step 5:** Determine if the flow is rotational at this point.\n"
|
| 366 |
+
f"A flow is irrotational at a point if its vorticity is zero there.\n"
|
| 367 |
+
f"Since the vorticity component zeta_z = {round(zeta_z, precision)}, which is {'zero' if is_irrotational else 'not zero'},\n"
|
| 368 |
+
f"the flow is {'irrotational' if is_irrotational else 'rotational'} at this specific point.\n\n"
|
| 369 |
+
|
| 370 |
+
f"**Answer:**\n"
|
| 371 |
+
f"The vorticity at ({x_point}, {y_point}) is (0, 0, {round(zeta_z, precision)}) rad/s. The flow is {'irrotational' if is_irrotational else 'rotational'} at this point."
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
else: # flow_type == '3D'
|
| 375 |
+
D = random.randint(-4, 4)
|
| 376 |
+
E = random.randint(-4, 4)
|
| 377 |
+
F = random.randint(-4, 4)
|
| 378 |
+
z_point = round(random.uniform(0.5, 3.0), 1)
|
| 379 |
+
|
| 380 |
+
# --- Define a more complex 3D velocity field ---
|
| 381 |
+
# u = A*y^2, v = B*z^2 + C*x, w = D*x^2 + E*y
|
| 382 |
+
# This allows all components of vorticity to be non-zero and position-dependent.
|
| 383 |
+
|
| 384 |
+
# --- Core calculations for 3D flow ---
|
| 385 |
+
du_dy_val = 2 * A * y_point
|
| 386 |
+
du_dz_val = 0
|
| 387 |
+
|
| 388 |
+
dv_dx_val = C
|
| 389 |
+
dv_dz_val = 2 * B * z_point
|
| 390 |
+
|
| 391 |
+
dw_dx_val = 2 * D * x_point
|
| 392 |
+
dw_dy_val = E
|
| 393 |
+
|
| 394 |
+
zeta_x = dw_dy_val - dv_dz_val
|
| 395 |
+
zeta_y = du_dz_val - dw_dx_val
|
| 396 |
+
zeta_z = dv_dx_val - du_dy_val
|
| 397 |
+
|
| 398 |
+
is_irrotational = (abs(zeta_x) < 1e-9 and abs(zeta_y) < 1e-9 and abs(zeta_z) < 1e-9)
|
| 399 |
+
|
| 400 |
+
# --- Generate question and solution strings for 3D flow ---
|
| 401 |
+
question = (
|
| 402 |
+
f"A 3D fluid flow is described by the velocity field:\n"
|
| 403 |
+
f"u = {A}y^2\n"
|
| 404 |
+
f"v = {B}z^2 + {C}x\n"
|
| 405 |
+
f"w = {D}x^2 + {E}y\n"
|
| 406 |
+
f"where u, v, w are in m/s, and x, y, z are in meters.\n\n"
|
| 407 |
+
f"Calculate the vorticity vector at the point ({x_point}, {y_point}, {z_point}) m and "
|
| 408 |
+
f"determine if the flow is rotational or irrotational at that point."
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
solution = (
|
| 412 |
+
f"**Given:**\n"
|
| 413 |
+
f"Velocity field: u={A}y^2, v={B}z^2 + {C}x, w={D}x^2 + {E}y\n"
|
| 414 |
+
f"Point of interest: ({x_point}, {y_point}, {z_point}) m\n\n"
|
| 415 |
+
|
| 416 |
+
f"**Step 1:** State the formula for the 3D vorticity vector.\n"
|
| 417 |
+
f"zeta = (dw/dy - dv/dz)i + (du/dz - dw/dx)j + (dv/dx - du/dy)k\n\n"
|
| 418 |
+
|
| 419 |
+
f"**Step 2:** Calculate all necessary partial derivatives.\n"
|
| 420 |
+
f"From u = {A}y^2: du/dy = {2*A}y, du/dz = 0\n"
|
| 421 |
+
f"From v = {B}z^2 + {C}x: dv/dx = {C}, dv/dz = {2*B}z\n"
|
| 422 |
+
f"From w = {D}x^2 + {E}y: dw/dx = {2*D}x, dw/dy = {E}\n\n"
|
| 423 |
+
|
| 424 |
+
f"**Step 3:** Evaluate the derivatives at the point ({x_point}, {y_point}, {z_point}).\n"
|
| 425 |
+
f" du/dy = {2*A}({y_point}) = {round(du_dy_val, precision)}\n"
|
| 426 |
+
f" dv/dx = {C}\n"
|
| 427 |
+
f" dv/dz = {2*B}({z_point}) = {round(dv_dz_val, precision)}\n"
|
| 428 |
+
f" dw/dx = {2*D}({x_point}) = {round(dw_dx_val, precision)}\n"
|
| 429 |
+
f" dw/dy = {E}\n"
|
| 430 |
+
f" (du/dz is always 0)\n\n"
|
| 431 |
+
|
| 432 |
+
f"**Step 4:** Calculate each component of the vorticity vector.\n"
|
| 433 |
+
f"zeta_x = dw/dy - dv/dz = {E} - ({round(dv_dz_val, precision)}) = {round(zeta_x, precision)}\n"
|
| 434 |
+
f"zeta_y = du/dz - dw/dx = 0 - ({round(dw_dx_val, precision)}) = {round(zeta_y, precision)}\n"
|
| 435 |
+
f"zeta_z = dv/dx - du/dy = {C} - ({round(du_dy_val, precision)}) = {round(zeta_z, precision)}\n\n"
|
| 436 |
+
|
| 437 |
+
f"**Step 5:** Assemble the vorticity vector and determine if the flow is rotational.\n"
|
| 438 |
+
f"The vorticity vector at this point is ({round(zeta_x, precision)}, {round(zeta_y, precision)}, {round(zeta_z, precision)}) rad/s.\n"
|
| 439 |
+
f"A flow is irrotational at a point if its vorticity is the zero vector (0, 0, 0).\n"
|
| 440 |
+
f"Since the vector is {'the zero vector' if is_irrotational else 'not the zero vector'},\n"
|
| 441 |
+
f"the flow is {'irrotational' if is_irrotational else 'rotational'} at this specific point.\n\n"
|
| 442 |
+
|
| 443 |
+
f"**Answer:**\n"
|
| 444 |
+
f"The vorticity vector at ({x_point}, {y_point}, {z_point}) is ({round(zeta_x, precision)}, {round(zeta_y, precision)}, {round(zeta_z, precision)}) rad/s. "
|
| 445 |
+
f"The flow is {'irrotational' if is_irrotational else 'rotational'} at this point."
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
return question, solution
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
# Template 4 (Advanced)
|
| 452 |
+
def template_particle_pathline():
|
| 453 |
+
"""
|
| 454 |
+
Fluid Kinematics: Finding a Particle's Pathline in a Steady Flow
|
| 455 |
+
|
| 456 |
+
Scenario:
|
| 457 |
+
This template distinguishes between the Eulerian description (velocity field)
|
| 458 |
+
and the Lagrangian description (individual particle path). It requires solving
|
| 459 |
+
a system of ordinary differential equations (ODEs) to trace a particle's
|
| 460 |
+
movement from a starting point to its position at a later time. The velocity
|
| 461 |
+
field is steady and designed so the variables are separable.
|
| 462 |
+
|
| 463 |
+
Core Equations:
|
| 464 |
+
Pathline Definition: dx/dt = u(x, y) and dy/dt = v(x, y)
|
| 465 |
+
Velocity Field Used: u = A*x, v = -B*y
|
| 466 |
+
|
| 467 |
+
Returns:
|
| 468 |
+
tuple: A tuple containing:
|
| 469 |
+
- str: A question asking for the particle's final coordinates.
|
| 470 |
+
- str: A step-by-step solution showing the derivation of the pathline equations.
|
| 471 |
+
"""
|
| 472 |
+
# 1. Parameterize the inputs with random values
|
| 473 |
+
A = random.randint(1, 5)
|
| 474 |
+
B = random.randint(1, 5)
|
| 475 |
+
|
| 476 |
+
x0 = round(random.uniform(0.5, 4.0), 1)
|
| 477 |
+
y0 = round(random.uniform(0.5, 4.0), 1)
|
| 478 |
+
tf = round(random.uniform(0.1, 2.0), 2)
|
| 479 |
+
|
| 480 |
+
precision = 4
|
| 481 |
+
|
| 482 |
+
# 2. Perform the core calculations for the solution
|
| 483 |
+
# Solve for the final position by integrating the velocity components
|
| 484 |
+
# x(t) = x0 * exp(A*t)
|
| 485 |
+
# y(t) = y0 * exp(-B*t)
|
| 486 |
+
xf = x0 * math.exp(A * tf)
|
| 487 |
+
yf = y0 * math.exp(-B * tf)
|
| 488 |
+
|
| 489 |
+
# 3. Generate the question and solution strings
|
| 490 |
+
question = (
|
| 491 |
+
f"A steady, 2D velocity field is given by u = {A}x and v = -{B}y. "
|
| 492 |
+
f"A fluid particle is located at the initial position (x0, y0) = ({x0}, {y0}) at time t=0.\n\n"
|
| 493 |
+
f"Determine the particle's coordinates (x, y) at time t = {tf} s."
|
| 494 |
+
)
|
| 495 |
+
|
| 496 |
+
solution = (
|
| 497 |
+
f"**Given:**\n"
|
| 498 |
+
f"Velocity field: u = {A}x, v = -{B}y\n"
|
| 499 |
+
f"Initial position (x0, y0) = ({x0}, {y0})\n"
|
| 500 |
+
f"Final time (tf) = {tf} s\n\n"
|
| 501 |
+
|
| 502 |
+
f"**Step 1:** Set up the differential equation for the x-coordinate.\n"
|
| 503 |
+
f"The pathline is defined by dx/dt = u.\n"
|
| 504 |
+
f" dx/dt = {A}x\n\n"
|
| 505 |
+
|
| 506 |
+
f"**Step 2:** Solve the ODE for x(t) by separating variables.\n"
|
| 507 |
+
f" (1/x) dx = {A} dt\n"
|
| 508 |
+
f"Integrating both sides gives:\n"
|
| 509 |
+
f" ln(x) = {A}t + C1\n"
|
| 510 |
+
f"Solving for x by exponentiating:\n"
|
| 511 |
+
f" x(t) = exp({A}t + C1) = C * exp({A}t)\n\n"
|
| 512 |
+
|
| 513 |
+
f"**Step 3:** Apply the initial condition x(0) = {x0} to find the constant C.\n"
|
| 514 |
+
f" {x0} = C * exp({A}*0) = C * 1\n"
|
| 515 |
+
f"So, C = {x0}. The specific solution for the x-coordinate is:\n"
|
| 516 |
+
f" x(t) = {x0} * exp({A}t)\n\n"
|
| 517 |
+
|
| 518 |
+
f"**Step 4:** Set up and solve the differential equation for the y-coordinate.\n"
|
| 519 |
+
f"The pathline is defined by dy/dt = v.\n"
|
| 520 |
+
f" dy/dt = -{B}y\n"
|
| 521 |
+
f"Separating variables: (1/y) dy = -{B} dt\n"
|
| 522 |
+
f"Integrating both sides gives:\n"
|
| 523 |
+
f" ln(y) = -{B}t + D1\n"
|
| 524 |
+
f" y(t) = D * exp(-{B}t)\n\n"
|
| 525 |
+
|
| 526 |
+
f"**Step 5:** Apply the initial condition y(0) = {y0} to find the constant D.\n"
|
| 527 |
+
f" {y0} = D * exp(-{B}*0) = D * 1\n"
|
| 528 |
+
f"So, D = {y0}. The specific solution for the y-coordinate is:\n"
|
| 529 |
+
f" y(t) = {y0} * exp(-{B}t)\n\n"
|
| 530 |
+
|
| 531 |
+
f"**Step 6:** Calculate the final position at t = {tf} s.\n"
|
| 532 |
+
f"Substitute t = {tf} into the pathline equations:\n"
|
| 533 |
+
f" x({tf}) = {x0} * exp({A} * {tf}) = {round(xf, precision)}\n"
|
| 534 |
+
f" y({tf}) = {y0} * exp(-{B} * {tf}) = {round(yf, precision)}\n\n"
|
| 535 |
+
|
| 536 |
+
f"**Answer:**\n"
|
| 537 |
+
f"At t = {tf} s, the particle's coordinates are ({round(xf, precision)}, {round(yf, precision)})."
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
return question, solution
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
# Template 5 (Advanced)
|
| 544 |
+
def template_incompressible_continuity():
|
| 545 |
+
"""
|
| 546 |
+
Fluid Kinematics: Deriving a Velocity Component for Incompressible Flow
|
| 547 |
+
|
| 548 |
+
Scenario:
|
| 549 |
+
This problem uses the differential form of the conservation of mass
|
| 550 |
+
(continuity equation) for a 2D, incompressible flow. Given one velocity
|
| 551 |
+
component, the user must find the other by performing partial differentiation
|
| 552 |
+
and integration. The problem asks for the simplest possible expression,
|
| 553 |
+
which implies the constant of integration is zero.
|
| 554 |
+
|
| 555 |
+
Core Equation:
|
| 556 |
+
2D Incompressible Continuity: du/dx + dv/dy = 0
|
| 557 |
+
|
| 558 |
+
Returns:
|
| 559 |
+
tuple: A tuple containing:
|
| 560 |
+
- str: A question asking for the unknown velocity component.
|
| 561 |
+
- str: A step-by-step solution showing the derivation.
|
| 562 |
+
"""
|
| 563 |
+
# 1. Parameterize the inputs with random values
|
| 564 |
+
|
| 565 |
+
# Randomly choose which velocity component is given
|
| 566 |
+
given_component = random.choice(['u', 'v'])
|
| 567 |
+
|
| 568 |
+
# Random non-zero coefficients for the polynomial terms
|
| 569 |
+
A = random.choice([i for i in range(-5, 6) if i != 0])
|
| 570 |
+
B = random.choice([i for i in range(-5, 6) if i != 0])
|
| 571 |
+
|
| 572 |
+
# 2. Perform calculations and generate strings based on the chosen component
|
| 573 |
+
if given_component == 'u':
|
| 574 |
+
# --- The u-component is given, find v ---
|
| 575 |
+
u_expr = f"{A}x^2 + {B}xy"
|
| 576 |
+
|
| 577 |
+
# Core calculations
|
| 578 |
+
# du/dx = 2*A*x + B*y
|
| 579 |
+
du_dx_expr = f"{2*A}x + {B}y"
|
| 580 |
+
# dv/dy = -du/dx
|
| 581 |
+
dv_dy_expr = f"-({du_dx_expr})"
|
| 582 |
+
# v = integral(dv/dy) dy = integral(-(2*A*x + B*y)) dy
|
| 583 |
+
v_expr = f"{-2*A}xy - {B/2.0}y^2"
|
| 584 |
+
|
| 585 |
+
question = (
|
| 586 |
+
f"A 2D, steady, incompressible flow has a velocity component in the x-direction "
|
| 587 |
+
f"given by u = {u_expr}.\n\n"
|
| 588 |
+
f"Determine the simplest possible expression for the y-component of velocity, v(x, y)."
|
| 589 |
+
)
|
| 590 |
+
|
| 591 |
+
solution = (
|
| 592 |
+
f"**Given:**\n"
|
| 593 |
+
f"The flow is 2D, steady, and incompressible.\n"
|
| 594 |
+
f"The u-component of velocity is u = {u_expr}\n\n"
|
| 595 |
+
|
| 596 |
+
f"**Step 1:** State the 2D incompressible continuity equation.\n"
|
| 597 |
+
f"The equation is: du/dx + dv/dy = 0\n\n"
|
| 598 |
+
|
| 599 |
+
f"**Step 2:** Calculate the partial derivative of the given u-component with respect to x.\n"
|
| 600 |
+
f" du/dx = d/dx({u_expr})\n"
|
| 601 |
+
f" du/dx = {du_dx_expr}\n\n"
|
| 602 |
+
|
| 603 |
+
f"**Step 3:** Rearrange the continuity equation to solve for dv/dy.\n"
|
| 604 |
+
f" dv/dy = -du/dx\n"
|
| 605 |
+
f" dv/dy = -({du_dx_expr}) = {-2*A}x - {B}y\n\n"
|
| 606 |
+
|
| 607 |
+
f"**Step 4:** Integrate dv/dy with respect to y to find v(x, y).\n"
|
| 608 |
+
f" v(x, y) = integral({-2*A}x - {B}y) dy\n"
|
| 609 |
+
f" v(x, y) = {-2*A}xy - ({B}/2)y^2 + f(x)\n"
|
| 610 |
+
f"The 'constant' of integration, f(x), is an arbitrary function of x.\n\n"
|
| 611 |
+
|
| 612 |
+
f"**Step 5:** Provide the simplest form for v(x, y).\n"
|
| 613 |
+
f"To find the simplest expression, we set the integration constant f(x) to zero.\n"
|
| 614 |
+
f" v(x, y) = {v_expr}\n\n"
|
| 615 |
+
|
| 616 |
+
f"**Answer:**\n"
|
| 617 |
+
f"The simplest expression for the y-component of velocity is v = {v_expr}."
|
| 618 |
+
)
|
| 619 |
+
|
| 620 |
+
else: # given_component == 'v'
|
| 621 |
+
# --- The v-component is given, find u ---
|
| 622 |
+
v_expr = f"{A}y^2 + {B}xy"
|
| 623 |
+
|
| 624 |
+
# Core calculations
|
| 625 |
+
# dv/dy = 2*A*y + B*x
|
| 626 |
+
dv_dy_expr = f"{2*A}y + {B}x"
|
| 627 |
+
# du/dx = -dv/dy
|
| 628 |
+
du_dx_expr = f"-({dv_dy_expr})"
|
| 629 |
+
# u = integral(du/dx) dx = integral(-(2*A*y + B*x)) dx
|
| 630 |
+
u_expr_sol = f"{-2*A}xy - {B/2.0}x^2"
|
| 631 |
+
|
| 632 |
+
question = (
|
| 633 |
+
f"A 2D, steady, incompressible flow has a velocity component in the y-direction "
|
| 634 |
+
f"given by v = {v_expr}.\n\n"
|
| 635 |
+
f"Determine the simplest possible expression for the x-component of velocity, u(x, y)."
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
+
solution = (
|
| 639 |
+
f"**Given:**\n"
|
| 640 |
+
f"The flow is 2D, steady, and incompressible.\n"
|
| 641 |
+
f"The v-component of velocity is v = {v_expr}\n\n"
|
| 642 |
+
|
| 643 |
+
f"**Step 1:** State the 2D incompressible continuity equation.\n"
|
| 644 |
+
f"The equation is: du/dx + dv/dy = 0\n\n"
|
| 645 |
+
|
| 646 |
+
f"**Step 2:** Calculate the partial derivative of the given v-component with respect to y.\n"
|
| 647 |
+
f" dv/dy = d/dy({v_expr})\n"
|
| 648 |
+
f" dv/dy = {dv_dy_expr}\n\n"
|
| 649 |
+
|
| 650 |
+
f"**Step 3:** Rearrange the continuity equation to solve for du/dx.\n"
|
| 651 |
+
f" du/dx = -dv/dy\n"
|
| 652 |
+
f" du/dx = -({dv_dy_expr}) = {-B}x - {2*A}y\n\n"
|
| 653 |
+
|
| 654 |
+
f"**Step 4:** Integrate du/dx with respect to x to find u(x, y).\n"
|
| 655 |
+
f" u(x, y) = integral({-B}x - {2*A}y) dx\n"
|
| 656 |
+
f" u(x, y) = -({B}/2)x^2 - {2*A}xy + g(y)\n"
|
| 657 |
+
f"The 'constant' of integration, g(y), is an arbitrary function of y.\n\n"
|
| 658 |
+
|
| 659 |
+
f"**Step 5:** Provide the simplest form for u(x, y).\n"
|
| 660 |
+
f"To find the simplest expression, we set the integration constant g(y) to zero.\n"
|
| 661 |
+
f" u(x, y) = {u_expr_sol}\n\n"
|
| 662 |
+
|
| 663 |
+
f"**Answer:**\n"
|
| 664 |
+
f"The simplest expression for the x-component of velocity is u = {u_expr_sol}."
|
| 665 |
+
)
|
| 666 |
+
|
| 667 |
+
return question, solution
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
def main():
|
| 671 |
+
"""
|
| 672 |
+
Generate numerous instances of each fluid kinematics template
|
| 673 |
+
with different random seeds and write the results to a JSONL file.
|
| 674 |
+
"""
|
| 675 |
+
import json
|
| 676 |
+
import os
|
| 677 |
+
|
| 678 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 679 |
+
output_file = "testset/mechanical_engineering/fluid_mechanics/fluid_kinematics.jsonl"
|
| 680 |
+
|
| 681 |
+
# Create the directory if it doesn't exist
|
| 682 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 683 |
+
|
| 684 |
+
# List of template functions with their ID and level
|
| 685 |
+
templates = [
|
| 686 |
+
(template_fluid_particle_acceleration, "fluid_particle_acceleration", "Easy"),
|
| 687 |
+
(template_volumetric_flow_rate, "volumetric_flow_rate", "Intermediate"),
|
| 688 |
+
(template_vorticity_check, "vorticity_check", "Intermediate"),
|
| 689 |
+
(template_particle_pathline, "particle_pathline", "Advanced"),
|
| 690 |
+
(template_incompressible_continuity, "incompressible_continuity", "Advanced"),
|
| 691 |
+
]
|
| 692 |
+
|
| 693 |
+
# List to store all generated problems
|
| 694 |
+
all_problems = []
|
| 695 |
+
|
| 696 |
+
# Generate problems for each template
|
| 697 |
+
for template_func, id_name, level in templates:
|
| 698 |
+
for _ in range(50):
|
| 699 |
+
# Generate a unique seed for each problem
|
| 700 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 701 |
+
random.seed(seed)
|
| 702 |
+
|
| 703 |
+
# Generate the problem and solution
|
| 704 |
+
question, solution = template_func()
|
| 705 |
+
|
| 706 |
+
# Create a JSON entry
|
| 707 |
+
problem_entry = {
|
| 708 |
+
"seed": seed,
|
| 709 |
+
"branch": "mechanical_engineering",
|
| 710 |
+
"domain": "fluid_mechanics",
|
| 711 |
+
"area": "fluid_kinematics",
|
| 712 |
+
"id": id_name,
|
| 713 |
+
"level": level,
|
| 714 |
+
"question": question,
|
| 715 |
+
"solution": solution
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
# Add to the list of problems
|
| 719 |
+
all_problems.append(problem_entry)
|
| 720 |
+
|
| 721 |
+
# Shuffle the problems to mix templates and levels
|
| 722 |
+
random.shuffle(all_problems)
|
| 723 |
+
|
| 724 |
+
# Write all problems to a .jsonl file
|
| 725 |
+
with open(output_file, "w") as file:
|
| 726 |
+
for problem in all_problems:
|
| 727 |
+
file.write(json.dumps(problem))
|
| 728 |
+
file.write("\n")
|
| 729 |
+
|
| 730 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 731 |
+
|
| 732 |
+
|
| 733 |
+
if __name__ == "__main__":
|
| 734 |
+
main()
|
data/templates/branches/mechanical_engineering/fluid_mechanics/fluid_statics.py
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
from data.templates.branches.mechanical_engineering.constants import GRAVITY, ATMOSPHERIC_PRESSURE_KPA, FLUID_DENSITIES, MATERIAL_DENSITIES, OBJECT_SHAPES, OBJECT_MATERIALS, PIPE_FLUIDS, MANOMETER_FLUIDS
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# Template 1 (Easy)
|
| 7 |
+
def template_hydrostatic_pressure_at_depth():
|
| 8 |
+
"""
|
| 9 |
+
Fluid Statics: Hydrostatic Pressure at Depth
|
| 10 |
+
|
| 11 |
+
Scenario:
|
| 12 |
+
This template generates a fundamental problem to calculate the absolute
|
| 13 |
+
pressure at a specified depth within a fluid. It considers both the
|
| 14 |
+
pressure exerted by the fluid column and the pressure at the free surface,
|
| 15 |
+
applying the basic hydrostatic pressure equation.
|
| 16 |
+
|
| 17 |
+
Core Equation:
|
| 18 |
+
p_absolute = p_surface + (rho * g * h)
|
| 19 |
+
|
| 20 |
+
Returns:
|
| 21 |
+
tuple: A tuple containing:
|
| 22 |
+
- str: A question asking for the absolute pressure at a certain depth.
|
| 23 |
+
- str: A step-by-step solution to the problem.
|
| 24 |
+
"""
|
| 25 |
+
# 1. Parameterize the inputs with random values
|
| 26 |
+
|
| 27 |
+
# Randomly select a fluid and its properties
|
| 28 |
+
fluid_name, density_rho = random.choice(list(FLUID_DENSITIES.items()))
|
| 29 |
+
|
| 30 |
+
# Randomize depth in meters
|
| 31 |
+
depth_h = round(random.uniform(5.0, 500.0), 2)
|
| 32 |
+
|
| 33 |
+
# Randomly decide if the surface pressure is atmospheric or a specified gauge pressure
|
| 34 |
+
is_surface_atmospheric = random.choice([True, False])
|
| 35 |
+
|
| 36 |
+
if is_surface_atmospheric:
|
| 37 |
+
surface_pressure_kpa = ATMOSPHERIC_PRESSURE_KPA
|
| 38 |
+
pressure_type_desc = "is atmospheric pressure"
|
| 39 |
+
else:
|
| 40 |
+
# Generate a random gauge pressure at the surface
|
| 41 |
+
surface_pressure_kpa = round(random.uniform(10.0, 200.0), 2)
|
| 42 |
+
pressure_type_desc = f"has a gauge pressure of {surface_pressure_kpa} kPa"
|
| 43 |
+
|
| 44 |
+
# Standardize precision for final outputs
|
| 45 |
+
precision = 3
|
| 46 |
+
|
| 47 |
+
# 2. Perform the core calculations for the solution
|
| 48 |
+
|
| 49 |
+
# Step A: Ensure all units are consistent (convert surface pressure to Pascals)
|
| 50 |
+
surface_pressure_pa = surface_pressure_kpa * 1000
|
| 51 |
+
|
| 52 |
+
# Step B: Calculate the pressure increase due to the fluid column (Gauge Pressure)
|
| 53 |
+
# This is the result of p_gauge = rho * g * h
|
| 54 |
+
pressure_increase_pa = density_rho * GRAVITY * depth_h
|
| 55 |
+
|
| 56 |
+
# Step C: Calculate the final absolute pressure in Pascals
|
| 57 |
+
absolute_pressure_pa = surface_pressure_pa + pressure_increase_pa
|
| 58 |
+
|
| 59 |
+
# Step D: Convert the final answer back to kilopascals (kPa) for readability
|
| 60 |
+
absolute_pressure_kpa = absolute_pressure_pa / 1000
|
| 61 |
+
|
| 62 |
+
# 3. Generate the question and solution strings
|
| 63 |
+
|
| 64 |
+
question = (
|
| 65 |
+
f"An object is located {depth_h} m below the surface of a tank containing "
|
| 66 |
+
f"{fluid_name.lower()}. The pressure at the free surface {pressure_type_desc}. "
|
| 67 |
+
f"Assuming the density of {fluid_name.lower()} is {density_rho} kg/m^3, "
|
| 68 |
+
f"what is the absolute pressure at this depth?"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
solution = (
|
| 72 |
+
f"**Given:**\n"
|
| 73 |
+
f"Fluid: {fluid_name}\n"
|
| 74 |
+
f"Density of Fluid (rho): {density_rho} kg/m^3\n"
|
| 75 |
+
f"Depth (h): {depth_h} m\n"
|
| 76 |
+
f"Surface Pressure (p_surface): {surface_pressure_kpa} kPa\n"
|
| 77 |
+
f"Acceleration due to Gravity (g): {GRAVITY} m/s^2\n\n"
|
| 78 |
+
|
| 79 |
+
f"**Step 1:** Ensure Consistent Units\n"
|
| 80 |
+
f"The calculations require pressure to be in Pascals (Pa) to be consistent with other SI units.\n"
|
| 81 |
+
f"p_surface = {surface_pressure_kpa} kPa * 1000 = {surface_pressure_pa:.2f} Pa\n\n"
|
| 82 |
+
|
| 83 |
+
f"**Step 2:** Calculate the Pressure Increase Due to the Fluid Column\n"
|
| 84 |
+
f"The pressure exerted by the fluid at a given depth is calculated using the formula: p_increase = rho * g * h.\n"
|
| 85 |
+
f"p_increase = {density_rho} kg/m^3 * {GRAVITY} m/s^2 * {depth_h} m\n"
|
| 86 |
+
f"p_increase = {pressure_increase_pa:.2f} Pa\n\n"
|
| 87 |
+
|
| 88 |
+
f"**Step 3:** Calculate the Absolute Pressure at the Specified Depth\n"
|
| 89 |
+
f"The absolute pressure is the sum of the surface pressure and the pressure increase due to the fluid column.\n"
|
| 90 |
+
f"Formula: p_absolute = p_surface + p_increase\n"
|
| 91 |
+
f"p_absolute = {surface_pressure_pa:.2f} Pa + {pressure_increase_pa:.2f} Pa\n"
|
| 92 |
+
f"p_absolute = {absolute_pressure_pa:.2f} Pa\n\n"
|
| 93 |
+
|
| 94 |
+
f"**Step 4:** Convert the Final Answer to Kilopascals (kPa)\n"
|
| 95 |
+
f"For convenience, we convert the final pressure from Pascals back to kilopascals.\n"
|
| 96 |
+
f"p_absolute = {absolute_pressure_pa:.2f} Pa / 1000 = {round(absolute_pressure_kpa, precision)} kPa\n\n"
|
| 97 |
+
|
| 98 |
+
f"**Answer:**\n"
|
| 99 |
+
f"The absolute pressure at a depth of {depth_h} m is {round(absolute_pressure_kpa, precision)} kPa."
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
return question, solution
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# Template 2 (Easy)
|
| 106 |
+
def template_basic_buoyant_force():
|
| 107 |
+
"""
|
| 108 |
+
Fluid Statics: Basic Buoyant Force on a Submerged Object
|
| 109 |
+
|
| 110 |
+
Scenario:
|
| 111 |
+
This template applies Archimedes' principle to calculate the buoyant
|
| 112 |
+
force on a fully submerged object with a known volume. It reinforces
|
| 113 |
+
the direct relationship between buoyant force and the weight of the
|
| 114 |
+
displaced fluid.
|
| 115 |
+
|
| 116 |
+
Core Equation:
|
| 117 |
+
F_buoyant = rho_fluid * g * V_displaced
|
| 118 |
+
|
| 119 |
+
Returns:
|
| 120 |
+
tuple: A tuple containing:
|
| 121 |
+
- str: A question asking for the buoyant force on an object.
|
| 122 |
+
- str: A step-by-step solution to the problem.
|
| 123 |
+
"""
|
| 124 |
+
# 1. Parameterize the inputs with random values
|
| 125 |
+
|
| 126 |
+
# Randomly select a fluid and its properties
|
| 127 |
+
fluid_name, density_rho = random.choice(list(FLUID_DENSITIES.items()))
|
| 128 |
+
|
| 129 |
+
# Randomly select descriptive properties for the object
|
| 130 |
+
shape = random.choice(OBJECT_SHAPES)
|
| 131 |
+
material = random.choice(OBJECT_MATERIALS)
|
| 132 |
+
|
| 133 |
+
# Randomize the object's volume in cubic meters
|
| 134 |
+
object_volume = round(random.uniform(0.05, 2.5), 3)
|
| 135 |
+
|
| 136 |
+
# Standardize precision for final outputs
|
| 137 |
+
precision = 3
|
| 138 |
+
|
| 139 |
+
# 2. Perform the core calculations for the solution
|
| 140 |
+
|
| 141 |
+
# Step A: Calculate the buoyant force in Newtons (N)
|
| 142 |
+
# Since the object is fully submerged, the displaced volume equals the object's volume.
|
| 143 |
+
buoyant_force_n = density_rho * GRAVITY * object_volume
|
| 144 |
+
|
| 145 |
+
# Step B: Convert to kilonewtons (kN) if the value is large, for better readability
|
| 146 |
+
buoyant_force_kn = buoyant_force_n / 1000
|
| 147 |
+
|
| 148 |
+
# 3. Generate the question and solution strings
|
| 149 |
+
|
| 150 |
+
question = (
|
| 151 |
+
f"A solid {material} {shape} with a total volume of {object_volume} m^3 is "
|
| 152 |
+
f"fully submerged in a tank of {fluid_name.lower()}. "
|
| 153 |
+
f"Given that the density of {fluid_name.lower()} is {density_rho} kg/m^3, "
|
| 154 |
+
f"calculate the buoyant force acting on the {shape}."
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
solution = (
|
| 158 |
+
f"**Given:**\n"
|
| 159 |
+
f"Object Volume (V): {object_volume} m^3\n"
|
| 160 |
+
f"Fluid: {fluid_name}\n"
|
| 161 |
+
f"Density of Fluid (rho): {density_rho} kg/m^3\n"
|
| 162 |
+
f"Acceleration due to Gravity (g): {GRAVITY} m/s^2\n\n"
|
| 163 |
+
|
| 164 |
+
f"**Step 1:** Identify the Principle\n"
|
| 165 |
+
f"According to Archimedes' principle, the buoyant force (F_buoyant) on a submerged "
|
| 166 |
+
f"object is equal to the weight of the fluid it displaces.\n"
|
| 167 |
+
f"Since the object is fully submerged, the volume of displaced fluid is equal to the "
|
| 168 |
+
f"volume of the object itself.\n\n"
|
| 169 |
+
|
| 170 |
+
f"**Step 2:** Apply the Buoyant Force Formula\n"
|
| 171 |
+
f"The formula for buoyant force is: F_buoyant = rho_fluid * g * V_displaced\n"
|
| 172 |
+
f"F_buoyant = {density_rho} kg/m^3 * {GRAVITY} m/s^2 * {object_volume} m^3\n"
|
| 173 |
+
f"F_buoyant = {round(buoyant_force_n, precision)} N\n\n"
|
| 174 |
+
|
| 175 |
+
f"**Step 3:** Convert the Result to Kilonewtons (kN) (Optional)\n"
|
| 176 |
+
f"For larger force values, it is common to express the result in kilonewtons.\n"
|
| 177 |
+
f"F_buoyant = {round(buoyant_force_n, precision)} N / 1000 = {round(buoyant_force_kn, precision)} kN\n\n"
|
| 178 |
+
|
| 179 |
+
f"**Answer:**\n"
|
| 180 |
+
f"The buoyant force acting on the {shape} is {round(buoyant_force_n, precision)} N, "
|
| 181 |
+
f"which is equivalent to {round(buoyant_force_kn, precision)} kN."
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
return question, solution
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
# Template 3 (Intermediate)
|
| 188 |
+
def template_utube_manometer():
|
| 189 |
+
"""
|
| 190 |
+
Fluid Statics: U-Tube Manometer Pressure Measurement
|
| 191 |
+
|
| 192 |
+
Scenario:
|
| 193 |
+
This template generates a classic problem involving a U-tube manometer
|
| 194 |
+
used to measure the gauge pressure of a fluid inside a pipe. The solution
|
| 195 |
+
requires applying the principle of hydrostatic equilibrium by balancing
|
| 196 |
+
the pressure contributions from different fluid columns.
|
| 197 |
+
|
| 198 |
+
Core Equation:
|
| 199 |
+
P_gauge = (rho_2 * g * h2) - (rho_1 * g * h1)
|
| 200 |
+
|
| 201 |
+
Returns:
|
| 202 |
+
tuple: A tuple containing:
|
| 203 |
+
- str: A question asking for the gauge pressure in a pipe.
|
| 204 |
+
- str: A step-by-step solution to the problem.
|
| 205 |
+
"""
|
| 206 |
+
# 1. Parameterize the inputs with random values
|
| 207 |
+
|
| 208 |
+
# Randomly select a fluid for the pipe
|
| 209 |
+
pipe_fluid_name, rho1 = random.choice(list(PIPE_FLUIDS.items()))
|
| 210 |
+
|
| 211 |
+
# Randomly select a fluid for the manometer
|
| 212 |
+
manometer_fluid_name, rho2 = random.choice(list(MANOMETER_FLUIDS.items()))
|
| 213 |
+
|
| 214 |
+
# --- Ensure physical realism: manometer fluid must be denser ---
|
| 215 |
+
# If the chosen pipe fluid is denser than the manometer fluid, re-select
|
| 216 |
+
# the manometer fluid until it is denser. This avoids nonsensical scenarios.
|
| 217 |
+
while rho2 <= rho1:
|
| 218 |
+
manometer_fluid_name, rho2 = random.choice(list(MANOMETER_FLUIDS.items()))
|
| 219 |
+
|
| 220 |
+
# Randomize the vertical heights, in meters
|
| 221 |
+
# h1: distance from pipe centerline down to the fluid interface
|
| 222 |
+
h1 = round(random.uniform(0.1, 0.5), 2)
|
| 223 |
+
# h2: the height difference between the two manometer fluid columns
|
| 224 |
+
h2 = round(random.uniform(0.05, 0.75), 2)
|
| 225 |
+
|
| 226 |
+
# Standardize precision for final outputs
|
| 227 |
+
precision = 3
|
| 228 |
+
|
| 229 |
+
# 2. Perform the core calculations for the solution
|
| 230 |
+
|
| 231 |
+
# Step A: Calculate the pressure contribution from the pipe fluid column (rho1*g*h1)
|
| 232 |
+
pressure_term1_pa = rho1 * GRAVITY * h1
|
| 233 |
+
|
| 234 |
+
# Step B: Calculate the pressure contribution from the manometer fluid column (rho2*g*h2)
|
| 235 |
+
pressure_term2_pa = rho2 * GRAVITY * h2
|
| 236 |
+
|
| 237 |
+
# Step C: Calculate the gauge pressure in Pascals (Pa)
|
| 238 |
+
gauge_pressure_pa = pressure_term2_pa - pressure_term1_pa
|
| 239 |
+
|
| 240 |
+
# Step D: Convert the final answer to kilopascals (kPa) for readability
|
| 241 |
+
gauge_pressure_kpa = gauge_pressure_pa / 1000
|
| 242 |
+
|
| 243 |
+
# 3. Generate the question and solution strings
|
| 244 |
+
|
| 245 |
+
question = (
|
| 246 |
+
f"A U-tube manometer using {manometer_fluid_name.lower()} (density = {rho2} kg/m^3) is "
|
| 247 |
+
f"connected to a pipe carrying {pipe_fluid_name.lower()} (density = {rho1} kg/m^3). "
|
| 248 |
+
f"The interface between the two fluids is {h1} m below the centerline of the pipe. "
|
| 249 |
+
f"The level of {manometer_fluid_name.lower()} in the arm open to the atmosphere is {h2} m "
|
| 250 |
+
f"higher than the interface. What is the gauge pressure in the pipe?"
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
solution = (
|
| 254 |
+
f"**Given:**\n"
|
| 255 |
+
f"Pipe Fluid: {pipe_fluid_name}, Density (rho1) = {rho1} kg/m^3\n"
|
| 256 |
+
f"Manometer Fluid: {manometer_fluid_name}, Density (rho2) = {rho2} kg/m^3\n"
|
| 257 |
+
f"Height from pipe centerline to interface (h1): {h1} m\n"
|
| 258 |
+
f"Height difference in manometer fluid (h2): {h2} m\n"
|
| 259 |
+
f"Acceleration due to Gravity (g): {GRAVITY} m/s^2\n\n"
|
| 260 |
+
|
| 261 |
+
f"**Step 1:** State the Principle of Manometry\n"
|
| 262 |
+
f"We can determine the pressure in the pipe by starting at the pipe's centerline, moving "
|
| 263 |
+
f"through the fluid columns to the open end, and balancing the pressures. The pressure at the "
|
| 264 |
+
f"same level within a continuous fluid at rest is equal.\n\n"
|
| 265 |
+
|
| 266 |
+
f"**Step 2:** Formulate the Pressure Balance Equation\n"
|
| 267 |
+
f"Let's establish a pressure equation starting from the pipe (P_pipe) and ending at the atmosphere (P_atm). "
|
| 268 |
+
f"The reference level is the interface between the pipe fluid and the manometer fluid.\n"
|
| 269 |
+
f"Pressure from pipe side at interface: P_pipe + (rho1 * g * h1)\n"
|
| 270 |
+
f"Pressure from atmosphere side at interface: P_atm + (rho2 * g * h2)\n"
|
| 271 |
+
f"Equating these gives: P_pipe + (rho1 * g * h1) = P_atm + (rho2 * g * h2)\n\n"
|
| 272 |
+
|
| 273 |
+
f"**Step 3:** Solve for the Gauge Pressure\n"
|
| 274 |
+
f"Gauge pressure is P_gauge = P_pipe - P_atm. Rearranging the equation:\n"
|
| 275 |
+
f"P_gauge = (rho2 * g * h2) - (rho1 * g * h1)\n\n"
|
| 276 |
+
|
| 277 |
+
f"**Step 4:** Calculate the Individual Pressure Terms\n"
|
| 278 |
+
f"Pressure from manometer fluid column = {rho2} * {GRAVITY} * {h2} = {pressure_term2_pa:.2f} Pa\n"
|
| 279 |
+
f"Pressure from pipe fluid column = {rho1} * {GRAVITY} * {h1} = {pressure_term1_pa:.2f} Pa\n\n"
|
| 280 |
+
|
| 281 |
+
f"**Step 5:** Calculate the Final Gauge Pressure\n"
|
| 282 |
+
f"P_gauge = {pressure_term2_pa:.2f} Pa - {pressure_term1_pa:.2f} Pa = {gauge_pressure_pa:.2f} Pa\n\n"
|
| 283 |
+
|
| 284 |
+
f"**Step 6:** Convert the Answer to Kilopascals (kPa)\n"
|
| 285 |
+
f"P_gauge = {gauge_pressure_pa:.2f} Pa / 1000 = {round(gauge_pressure_kpa, precision)} kPa\n\n"
|
| 286 |
+
|
| 287 |
+
f"**Answer:**\n"
|
| 288 |
+
f"The gauge pressure in the pipe is {round(gauge_pressure_kpa, precision)} kPa."
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
return question, solution
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
# Template 4 (Intermediate)
|
| 295 |
+
def template_floating_object_submersion_depth():
|
| 296 |
+
"""
|
| 297 |
+
Fluid Statics: Floating Object Submersion Depth
|
| 298 |
+
|
| 299 |
+
Scenario:
|
| 300 |
+
This template applies the principle of buoyancy to determine how deep
|
| 301 |
+
an object with a uniform cross-section (like a rectangular block or
|
| 302 |
+
cylinder) will float in a liquid. The solution requires equating the
|
| 303 |
+
object's total weight to the buoyant force acting on its submerged part.
|
| 304 |
+
|
| 305 |
+
Core Equations:
|
| 306 |
+
- Weight (W) = rho_object * g * V_total
|
| 307 |
+
- Buoyant Force (F_B) = rho_fluid * g * V_submerged
|
| 308 |
+
- At equilibrium (floating): W = F_B
|
| 309 |
+
- This simplifies to: rho_object * V_total = rho_fluid * V_submerged
|
| 310 |
+
|
| 311 |
+
Returns:
|
| 312 |
+
tuple: A tuple containing:
|
| 313 |
+
- str: A question asking for the submersion depth of a floating object.
|
| 314 |
+
- str: A step-by-step solution to the problem.
|
| 315 |
+
"""
|
| 316 |
+
# 1. Parameterize the inputs to ensure a valid floating scenario
|
| 317 |
+
|
| 318 |
+
# Randomly select an object material and a fluid
|
| 319 |
+
obj_material, rho_object = random.choice(list(MATERIAL_DENSITIES.items()))
|
| 320 |
+
fluid_name, rho_fluid = random.choice(list(FLUID_DENSITIES.items()))
|
| 321 |
+
|
| 322 |
+
# CRITICAL: Ensure the object will actually float ---
|
| 323 |
+
# Re-select until the object's density is less than the fluid's density.
|
| 324 |
+
max_attempts = 20
|
| 325 |
+
attempt = 0
|
| 326 |
+
while rho_object >= rho_fluid and attempt < max_attempts:
|
| 327 |
+
obj_material, rho_object = random.choice(list(MATERIAL_DENSITIES.items()))
|
| 328 |
+
fluid_name, rho_fluid = random.choice(list(FLUID_DENSITIES.items()))
|
| 329 |
+
attempt += 1
|
| 330 |
+
|
| 331 |
+
# Fallback in the rare case a valid pair isn't found quickly
|
| 332 |
+
if rho_object >= rho_fluid:
|
| 333 |
+
obj_material, rho_object = "Pine Wood", 500
|
| 334 |
+
fluid_name, rho_fluid = "Fresh Water", 998
|
| 335 |
+
|
| 336 |
+
# Randomly choose the object's shape (uniform cross-section)
|
| 337 |
+
shape = random.choice(["rectangular block", "cylinder"])
|
| 338 |
+
|
| 339 |
+
# Randomize dimensions based on shape
|
| 340 |
+
if shape == "rectangular block":
|
| 341 |
+
length = round(random.uniform(0.5, 3.0), 2)
|
| 342 |
+
width = round(random.uniform(0.2, 2.0), 2)
|
| 343 |
+
height = round(random.uniform(0.1, 1.5), 2) # This is the total vertical height
|
| 344 |
+
shape_dims_str = f"dimensions {length} m (length) x {width} m (width) x {height} m (height)"
|
| 345 |
+
shape_dims_given_str = (f" - Length (L): {length} m\n"
|
| 346 |
+
f" - Width (W): {width} m\n"
|
| 347 |
+
f" - Total Height (H): {height} m")
|
| 348 |
+
else: # shape == "cylinder"
|
| 349 |
+
radius = round(random.uniform(0.1, 1.5), 2)
|
| 350 |
+
height = round(random.uniform(0.2, 2.5), 2) # This is the total vertical height
|
| 351 |
+
shape_dims_str = f"a radius of {radius} m and a total height of {height} m"
|
| 352 |
+
shape_dims_given_str = (f" - Radius (r): {radius} m\n"
|
| 353 |
+
f" - Total Height (H): {height} m")
|
| 354 |
+
|
| 355 |
+
# Standardize precision for final outputs
|
| 356 |
+
precision = 4
|
| 357 |
+
|
| 358 |
+
# 2. Perform the core calculations for the solution
|
| 359 |
+
|
| 360 |
+
# The core insight is that for a uniform cross-section, the area cancels out.
|
| 361 |
+
# W = F_B => rho_obj * g * (Area * H) = rho_fluid * g * (Area * h_sub)
|
| 362 |
+
# Simplifying gives: rho_obj * H = rho_fluid * h_sub
|
| 363 |
+
submersion_depth = (rho_object / rho_fluid) * height
|
| 364 |
+
|
| 365 |
+
# 3. Generate the question and solution strings
|
| 366 |
+
|
| 367 |
+
question = (
|
| 368 |
+
f"A {shape} made of {obj_material.lower()} (density = {rho_object} kg/m^3) has "
|
| 369 |
+
f"{shape_dims_str}. The object is placed in a large tank of {fluid_name.lower()} "
|
| 370 |
+
f"(density = {rho_fluid} kg/m^3). Assuming the object floats in a stable, upright "
|
| 371 |
+
f"position, calculate its submersion depth (the vertical height of the object "
|
| 372 |
+
f"that is below the fluid surface)."
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
solution = (
|
| 376 |
+
f"**Given:**\n"
|
| 377 |
+
f"Object Shape: {shape.capitalize()}\n"
|
| 378 |
+
f"{shape_dims_given_str}\n"
|
| 379 |
+
f"Object Density (rho_obj): {rho_object} kg/m^3\n"
|
| 380 |
+
f"Fluid Density (rho_fluid): {rho_fluid} kg/m^3\n\n"
|
| 381 |
+
|
| 382 |
+
f"**Step 1:** State the Principle of Flotation\n"
|
| 383 |
+
f"For an object to float, its total weight (W) must be equal to the buoyant force (F_B) "
|
| 384 |
+
f"exerted by the fluid. The buoyant force is the weight of the displaced fluid.\n"
|
| 385 |
+
f"Equilibrium Condition: W = F_B\n\n"
|
| 386 |
+
|
| 387 |
+
f"**Step 2:** Express Weight and Buoyant Force using Densities\n"
|
| 388 |
+
f"Weight (W) = rho_obj * g * V_total\n"
|
| 389 |
+
f"Buoyant Force (F_B) = rho_fluid * g * V_submerged\n\n"
|
| 390 |
+
f"Setting them equal: \n"
|
| 391 |
+
f"rho_obj * g * V_total = rho_fluid * g * V_submerged\n\n"
|
| 392 |
+
|
| 393 |
+
f"**Step 3:** Simplify for a Uniform Cross-Section\n"
|
| 394 |
+
f"The acceleration of gravity (g) cancels from both sides. For an object with a uniform "
|
| 395 |
+
f"cross-sectional area (A), the volumes can be expressed as V = A * height.\n"
|
| 396 |
+
f"V_total = A * H_total\n"
|
| 397 |
+
f"V_submerged = A * h_submerged\n\n"
|
| 398 |
+
f"Substituting these into the equation:\n"
|
| 399 |
+
f"rho_obj * (A * H_total) = rho_fluid * (A * h_submerged)\n\n"
|
| 400 |
+
f"The cross-sectional area (A) also cancels out, leaving a simple ratio:\n"
|
| 401 |
+
f"rho_obj * H_total = rho_fluid * h_submerged\n\n"
|
| 402 |
+
|
| 403 |
+
f"**Step 4:** Solve for the Submersion Depth (h_submerged)\n"
|
| 404 |
+
f"Rearranging the formula to solve for the unknown depth:\n"
|
| 405 |
+
f"h_submerged = (rho_obj / rho_fluid) * H_total\n\n"
|
| 406 |
+
f"Plugging in the given values:\n"
|
| 407 |
+
f"h_submerged = ({rho_object} / {rho_fluid}) * {height}\n"
|
| 408 |
+
f"h_submerged = {round(submersion_depth, precision)} m\n\n"
|
| 409 |
+
|
| 410 |
+
f"**Answer:**\n"
|
| 411 |
+
f"The submersion depth of the {shape} is {round(submersion_depth, precision)} m."
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
return question, solution
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
# Template 5 (Advanced)
|
| 418 |
+
def template_hydrostatic_force_on_plane():
|
| 419 |
+
"""
|
| 420 |
+
Fluid Statics: Hydrostatic Force and Center of Pressure on a Submerged Plane
|
| 421 |
+
|
| 422 |
+
Scenario:
|
| 423 |
+
This template generates a comprehensive problem requiring the calculation of
|
| 424 |
+
the magnitude of the resultant hydrostatic force on a submerged plane
|
| 425 |
+
surface (e.g., a gate, window) and the location where this force acts,
|
| 426 |
+
known as the center of pressure.
|
| 427 |
+
|
| 428 |
+
Core Equations:
|
| 429 |
+
- Resultant Force (F_R) = rho * g * h_c * A
|
| 430 |
+
- Center of Pressure (y_R) = y_c + (I_xc / (y_c * A))
|
| 431 |
+
where:
|
| 432 |
+
- rho = fluid density
|
| 433 |
+
- g = acceleration of gravity
|
| 434 |
+
- h_c = vertical depth from free surface to the centroid of the area
|
| 435 |
+
- A = area of the submerged surface
|
| 436 |
+
- y_c = inclined distance from free surface to the centroid
|
| 437 |
+
- I_xc = area moment of inertia about the centroidal axis parallel to the surface
|
| 438 |
+
|
| 439 |
+
Returns:
|
| 440 |
+
tuple: A tuple containing:
|
| 441 |
+
- str: A question about hydrostatic force and center of pressure.
|
| 442 |
+
- str: A detailed, step-by-step solution.
|
| 443 |
+
"""
|
| 444 |
+
# 1. Parameterize the inputs with random values
|
| 445 |
+
|
| 446 |
+
# Select fluid and shape
|
| 447 |
+
fluid_name, rho = random.choice(list(FLUID_DENSITIES.items()))
|
| 448 |
+
shape = random.choice(["rectangle", "circle"])
|
| 449 |
+
|
| 450 |
+
# Set geometric and positional parameters
|
| 451 |
+
angle_deg = random.choice([30, 45, 60, 90]) # Angle with the horizontal
|
| 452 |
+
angle_rad = math.radians(angle_deg)
|
| 453 |
+
h_top = round(random.uniform(0.5, 5.0), 2) # Vertical depth to top edge of the plane
|
| 454 |
+
|
| 455 |
+
# Randomize dimensions based on shape and calculate geometric properties
|
| 456 |
+
if shape == "rectangle":
|
| 457 |
+
b = round(random.uniform(1.0, 4.0), 2) # width
|
| 458 |
+
h = round(random.uniform(1.0, 4.0), 2) # height
|
| 459 |
+
area = b * h
|
| 460 |
+
I_xc = (b * h**3) / 12
|
| 461 |
+
dist_to_centroid_along_plane = h / 2
|
| 462 |
+
shape_desc = f"a rectangular gate with a width of {b} m and a height of {h} m"
|
| 463 |
+
shape_given = f" - Shape: Rectangle (width b = {b} m, height h = {h} m)"
|
| 464 |
+
else: # shape == "circle"
|
| 465 |
+
r = round(random.uniform(0.5, 2.0), 2) # radius
|
| 466 |
+
area = math.pi * r**2
|
| 467 |
+
I_xc = (math.pi * r**4) / 4
|
| 468 |
+
dist_to_centroid_along_plane = r
|
| 469 |
+
shape_desc = f"a circular viewport with a radius of {r} m"
|
| 470 |
+
shape_given = f" - Shape: Circle (radius r = {r} m)"
|
| 471 |
+
|
| 472 |
+
precision = 4
|
| 473 |
+
|
| 474 |
+
# 2. Perform the core calculations for the solution
|
| 475 |
+
|
| 476 |
+
# Step A: Determine the position of the centroid (yc and hc)
|
| 477 |
+
# yc is the inclined distance from the free surface to the centroid
|
| 478 |
+
# hc is the vertical depth from the free surface to the centroid
|
| 479 |
+
if angle_deg == 90:
|
| 480 |
+
y_top = h_top
|
| 481 |
+
angle_desc = "is positioned vertically"
|
| 482 |
+
else:
|
| 483 |
+
y_top = h_top / math.sin(angle_rad)
|
| 484 |
+
angle_desc = f"is inclined at an angle of {angle_deg} degrees to the horizontal"
|
| 485 |
+
|
| 486 |
+
y_c = y_top + dist_to_centroid_along_plane
|
| 487 |
+
h_c = y_c * math.sin(angle_rad)
|
| 488 |
+
|
| 489 |
+
# Step B: Calculate the resultant force (F_R)
|
| 490 |
+
force_resultant_N = rho * GRAVITY * h_c * area
|
| 491 |
+
force_resultant_kN = force_resultant_N / 1000
|
| 492 |
+
|
| 493 |
+
# Step C: Calculate the location of the center of pressure (y_R)
|
| 494 |
+
# y_R is the inclined distance from the free surface to the center of pressure
|
| 495 |
+
y_R = y_c + (I_xc / (y_c * area))
|
| 496 |
+
|
| 497 |
+
# 3. Generate the question and solution strings
|
| 498 |
+
|
| 499 |
+
question = (
|
| 500 |
+
f"A submerged {shape_desc} {angle_desc} in a tank of {fluid_name.lower()} "
|
| 501 |
+
f"(density = {rho} kg/m^3). The top edge of the gate is {h_top} m vertically below "
|
| 502 |
+
f"the free surface. Calculate:\n"
|
| 503 |
+
f" a) The magnitude of the resultant hydrostatic force on the gate.\n"
|
| 504 |
+
f" b) The location of the center of pressure, measured along the incline of the gate "
|
| 505 |
+
f"from the free surface."
|
| 506 |
+
)
|
| 507 |
+
|
| 508 |
+
solution = (
|
| 509 |
+
f"**Given:**\n"
|
| 510 |
+
f"{shape_given}\n"
|
| 511 |
+
f"Fluid: {fluid_name} (rho = {rho} kg/m^3)\n"
|
| 512 |
+
f"Vertical depth to top edge (h_top): {h_top} m\n"
|
| 513 |
+
f"Angle of inclination (theta): {angle_deg} degrees\n\n"
|
| 514 |
+
|
| 515 |
+
f"**Step 1:** Calculate Geometric Properties of the Gate\n"
|
| 516 |
+
f"Area (A): {area:.{precision}f} m^2\n"
|
| 517 |
+
f"Moment of Inertia about centroid (I_xc): {I_xc:.{precision}f} m^4\n\n"
|
| 518 |
+
|
| 519 |
+
f"**Step 2:** Determine the Location of the Centroid (yc and hc)\n"
|
| 520 |
+
f"The centroid is the geometric center of the gate. We need its position relative to the free surface.\n"
|
| 521 |
+
f" - 'y' distances are measured along the plane's incline.\n"
|
| 522 |
+
f" - 'h' distances are measured vertically.\n\n"
|
| 523 |
+
f"Inclined distance from surface to top edge (y_top) = h_top / sin(theta)\n"
|
| 524 |
+
f" y_top = {h_top} / sin({angle_deg}°) = {y_top:.{precision}f} m\n"
|
| 525 |
+
f"Distance from top edge to centroid along plane = {dist_to_centroid_along_plane:.{precision}f} m\n"
|
| 526 |
+
f"Inclined distance from surface to centroid (y_c) = y_top + dist_to_centroid\n"
|
| 527 |
+
f" y_c = {y_top:.{precision}f} + {dist_to_centroid_along_plane:.{precision}f} = {y_c:.{precision}f} m\n\n"
|
| 528 |
+
f"Vertical depth to centroid (h_c) = y_c * sin(theta)\n"
|
| 529 |
+
f" h_c = {y_c:.{precision}f} * sin({angle_deg}°) = {h_c:.{precision}f} m\n\n"
|
| 530 |
+
|
| 531 |
+
f"**Step 3:** Calculate the Resultant Hydrostatic Force (F_R)\n"
|
| 532 |
+
f"The force is the pressure at the centroid multiplied by the total area.\n"
|
| 533 |
+
f"F_R = rho * g * h_c * A\n"
|
| 534 |
+
f"F_R = {rho} * {GRAVITY} * {h_c:.{precision}f} * {area:.{precision}f}\n"
|
| 535 |
+
f"F_R = {force_resultant_N:.2f} N\n"
|
| 536 |
+
f"F_R = {force_resultant_kN:.{precision}f} kN\n\n"
|
| 537 |
+
|
| 538 |
+
f"**Step 4:** Calculate the Center of Pressure (y_R)\n"
|
| 539 |
+
f"The center of pressure is the point where the resultant force acts. It is always below the centroid.\n"
|
| 540 |
+
f"y_R = y_c + (I_xc / (y_c * A))\n"
|
| 541 |
+
f"y_R = {y_c:.{precision}f} + ({I_xc:.{precision}f} / ({y_c:.{precision}f} * {area:.{precision}f}))\n"
|
| 542 |
+
f"y_R = {y_R:.{precision}f} m\n\n"
|
| 543 |
+
|
| 544 |
+
f"**Answer:**\n"
|
| 545 |
+
f"a) The magnitude of the resultant hydrostatic force is **{force_resultant_kN:.{precision}f} kN**.\n"
|
| 546 |
+
f"b) The center of pressure is located **{y_R:.{precision}f} m** from the free surface, measured down along the angle of the gate."
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
return question, solution
|
| 550 |
+
|
| 551 |
+
|
| 552 |
+
def main():
|
| 553 |
+
"""
|
| 554 |
+
Generate numerous instances of each fluid statics template
|
| 555 |
+
with different random seeds and write the results to a JSONL file.
|
| 556 |
+
"""
|
| 557 |
+
import json
|
| 558 |
+
import os
|
| 559 |
+
|
| 560 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 561 |
+
output_file = "testset/mechanical_engineering/fluid_mechanics/fluid_statics.jsonl"
|
| 562 |
+
|
| 563 |
+
# Create the directory if it doesn't exist
|
| 564 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 565 |
+
|
| 566 |
+
# List of template functions with their ID and level
|
| 567 |
+
templates = [
|
| 568 |
+
(template_hydrostatic_pressure_at_depth, "hydrostatic_pressure_at_depth", "Easy"),
|
| 569 |
+
(template_basic_buoyant_force, "basic_buoyant_force", "Easy"),
|
| 570 |
+
(template_utube_manometer, "utube_manometer", "Intermediate"),
|
| 571 |
+
(template_floating_object_submersion_depth, "floating_object_submersion_depth", "Intermediate"),
|
| 572 |
+
(template_hydrostatic_force_on_plane, "hydrostatic_force_on_plane", "Advanced"),
|
| 573 |
+
]
|
| 574 |
+
|
| 575 |
+
# List to store all generated problems
|
| 576 |
+
all_problems = []
|
| 577 |
+
|
| 578 |
+
# Generate problems for each template
|
| 579 |
+
for template_func, id_name, level in templates:
|
| 580 |
+
for _ in range(50):
|
| 581 |
+
# Generate a unique seed for each problem
|
| 582 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 583 |
+
random.seed(seed)
|
| 584 |
+
|
| 585 |
+
# Generate the problem and solution
|
| 586 |
+
question, solution = template_func()
|
| 587 |
+
|
| 588 |
+
# Create a JSON entry
|
| 589 |
+
problem_entry = {
|
| 590 |
+
"seed": seed,
|
| 591 |
+
"branch": "mechanical_engineering",
|
| 592 |
+
"domain": "fluid_mechanics",
|
| 593 |
+
"area": "fluid_statics",
|
| 594 |
+
"id": id_name,
|
| 595 |
+
"level": level,
|
| 596 |
+
"question": question,
|
| 597 |
+
"solution": solution
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
# Add to the list of problems
|
| 601 |
+
all_problems.append(problem_entry)
|
| 602 |
+
|
| 603 |
+
# Shuffle the problems to mix templates and levels
|
| 604 |
+
random.shuffle(all_problems)
|
| 605 |
+
|
| 606 |
+
# Write all problems to a .jsonl file
|
| 607 |
+
with open(output_file, "w") as file:
|
| 608 |
+
for problem in all_problems:
|
| 609 |
+
file.write(json.dumps(problem))
|
| 610 |
+
file.write("\n")
|
| 611 |
+
|
| 612 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 613 |
+
|
| 614 |
+
|
| 615 |
+
if __name__ == "__main__":
|
| 616 |
+
main()
|
data/templates/branches/mechanical_engineering/mechanics_of_materials/stress_and_strain.py
ADDED
|
@@ -0,0 +1,916 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
from data.templates.branches.mechanical_engineering.constants import MATERIAL_PROPERTIES
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# Template 1 (Easy)
|
| 7 |
+
def template_basic_stress_strain():
|
| 8 |
+
"""
|
| 9 |
+
Basic Normal Stress and Strain
|
| 10 |
+
|
| 11 |
+
Scenario:
|
| 12 |
+
This template tests the fundamental definitions of normal stress (sigma) and
|
| 13 |
+
normal strain (epsilon). Given a simple prismatic bar with a known geometry
|
| 14 |
+
and an applied axial load, the user must calculate these two basic quantities.
|
| 15 |
+
|
| 16 |
+
Core Equations:
|
| 17 |
+
Normal Stress: sigma = P / A
|
| 18 |
+
Normal Strain: epsilon = delta / L
|
| 19 |
+
|
| 20 |
+
Returns:
|
| 21 |
+
tuple: A tuple containing:
|
| 22 |
+
- str: A question asking for the normal stress and strain.
|
| 23 |
+
- str: A step-by-step solution showing the calculations.
|
| 24 |
+
"""
|
| 25 |
+
# 1. Parameterize inputs with random values, choosing a unit system first.
|
| 26 |
+
use_si_units = random.choice([True, False])
|
| 27 |
+
shape = random.choice(['circular', 'square'])
|
| 28 |
+
precision = 3 # Standardize precision for numerical stability and formatting
|
| 29 |
+
|
| 30 |
+
if use_si_units:
|
| 31 |
+
# --- SI Unit System ---
|
| 32 |
+
load = random.randint(10, 500) # in kN
|
| 33 |
+
length = round(random.uniform(0.5, 4.0), 2) # in meters
|
| 34 |
+
elongation = round(random.uniform(0.5, 5.0), 2) # in mm
|
| 35 |
+
|
| 36 |
+
if shape == 'circular':
|
| 37 |
+
dimension_val = random.randint(20, 100) # diameter in mm
|
| 38 |
+
dimension_name = "diameter"
|
| 39 |
+
area = math.pi * (dimension_val / 2)**2 # mm²
|
| 40 |
+
dim_str = f"{dimension_val} mm"
|
| 41 |
+
else: # square
|
| 42 |
+
dimension_val = random.randint(20, 100) # side length in mm
|
| 43 |
+
dimension_name = "side length"
|
| 44 |
+
area = dimension_val**2 # mm²
|
| 45 |
+
dim_str = f"{dimension_val} mm"
|
| 46 |
+
|
| 47 |
+
load_str = f"{load} kN"
|
| 48 |
+
length_str = f"{length} m"
|
| 49 |
+
elongation_str = f"{elongation} mm"
|
| 50 |
+
|
| 51 |
+
# Core Calculations (using units for clarity: N, mm, MPa)
|
| 52 |
+
# 1 MPa = 1 N/mm^2
|
| 53 |
+
stress = (load * 1000) / area # Stress in MPa
|
| 54 |
+
# Strain (unitless, but convert units to be consistent)
|
| 55 |
+
# Elongation in mm, Length in m -> convert length to mm
|
| 56 |
+
strain = elongation / (length * 1000)
|
| 57 |
+
|
| 58 |
+
stress_unit = "MPa"
|
| 59 |
+
strain_unit_explanation = "mm/m or unitless"
|
| 60 |
+
|
| 61 |
+
else:
|
| 62 |
+
# --- US Customary Unit System ---
|
| 63 |
+
load = random.randint(5, 100) # in kips
|
| 64 |
+
length = round(random.uniform(24.0, 120.0), 1) # in inches
|
| 65 |
+
elongation = round(random.uniform(0.05, 0.25), 3) # in inches
|
| 66 |
+
|
| 67 |
+
if shape == 'circular':
|
| 68 |
+
dimension_val = round(random.uniform(1.0, 5.0), 2) # diameter in inches
|
| 69 |
+
dimension_name = "diameter"
|
| 70 |
+
area = math.pi * (dimension_val / 2)**2 # in²
|
| 71 |
+
dim_str = f"{dimension_val} in"
|
| 72 |
+
else: # square
|
| 73 |
+
dimension_val = round(random.uniform(1.0, 5.0), 2) # side length in inches
|
| 74 |
+
dimension_name = "side length"
|
| 75 |
+
area = dimension_val**2
|
| 76 |
+
dim_str = f"{dimension_val} in"
|
| 77 |
+
|
| 78 |
+
load_str = f"{load} kips"
|
| 79 |
+
length_str = f"{length} in"
|
| 80 |
+
elongation_str = f"{elongation} in"
|
| 81 |
+
|
| 82 |
+
# Core Calculations (using units for clarity: kips, in, ksi)
|
| 83 |
+
# 1 ksi = 1 kip/in^2
|
| 84 |
+
stress = load / area # Stress in ksi
|
| 85 |
+
# Strain (unitless, since both length and elongation are in inches)
|
| 86 |
+
strain = elongation / length
|
| 87 |
+
|
| 88 |
+
stress_unit = "ksi"
|
| 89 |
+
strain_unit_explanation = "in/in or unitless"
|
| 90 |
+
|
| 91 |
+
# 2. Generate the question and solution strings
|
| 92 |
+
question = (
|
| 93 |
+
f"A prismatic bar with a length of {length_str} has a {shape} cross-section "
|
| 94 |
+
f"with a {dimension_name} of {dim_str}. The bar is subjected to an axial "
|
| 95 |
+
f"tensile load of {load_str}, causing it to elongate by {elongation_str}.\n\n"
|
| 96 |
+
f"Determine the following:\n"
|
| 97 |
+
f"a) The normal stress in the bar.\n"
|
| 98 |
+
f"b) The normal strain in the bar."
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
solution = (
|
| 102 |
+
f"**Given:**\n"
|
| 103 |
+
f"Load (P): {load_str}\n"
|
| 104 |
+
f"Length (L): {length_str}\n"
|
| 105 |
+
f"Elongation (delta): {elongation_str}\n"
|
| 106 |
+
f"Cross-Section: {shape.capitalize()} with {dimension_name} = {dim_str}\n\n"
|
| 107 |
+
|
| 108 |
+
f"**Step 1:** Calculate the Cross-Sectional Area (A)\n"
|
| 109 |
+
f"The area of a {shape} cross-section is calculated as follows:\n"
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
if shape == 'circular':
|
| 113 |
+
solution += (
|
| 114 |
+
f"A = pi * (d/2)^2 = pi * ({dimension_val}/2)^2 = {round(area, precision+1)} "
|
| 115 |
+
f"{'mm^2' if use_si_units else 'in^2'}\n\n"
|
| 116 |
+
)
|
| 117 |
+
else: # square
|
| 118 |
+
solution += (
|
| 119 |
+
f"A = side^2 = ({dimension_val})^2 = {round(area, precision+1)} "
|
| 120 |
+
f"{'mm^2' if use_si_units else 'in^2'}\n\n"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
solution += (
|
| 124 |
+
f"**Step 2:** Calculate the Normal Stress (sigma)\n"
|
| 125 |
+
f"Normal stress is defined as the force per unit area: sigma = P / A.\n"
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
if use_si_units:
|
| 129 |
+
solution += (
|
| 130 |
+
f"To get the result in Megapascals (MPa), we use the load in Newtons (N) and the area in mm^2 (1 MPa = 1 N/mm^2).\n"
|
| 131 |
+
f"P = {load} kN = {load * 1000} N\n"
|
| 132 |
+
f"A = {round(area, precision+1)} mm^2\n"
|
| 133 |
+
f"sigma = ({load * 1000} N) / ({round(area, precision+1)} mm^2) = {round(stress, precision)} MPa\n\n"
|
| 134 |
+
)
|
| 135 |
+
else: # US units
|
| 136 |
+
solution += (
|
| 137 |
+
f"To get the result in kips per square inch (ksi), we use the load in kips and the area in in^2 (1 ksi = 1 kip/in^2).\n"
|
| 138 |
+
f"P = {load} kips\n"
|
| 139 |
+
f"A = {round(area, precision+1)} in^2\n"
|
| 140 |
+
f"sigma = ({load} kips) / ({round(area, precision+1)} in^2) = {round(stress, precision)} ksi\n\n"
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
solution += (
|
| 144 |
+
f"**Step 3:** Calculate the Normal Strain (epsilon)\n"
|
| 145 |
+
f"Normal strain is the change in length per unit of original length: epsilon = delta / L.\n"
|
| 146 |
+
f"It is a dimensionless quantity, but units must be consistent for calculation.\n"
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
if use_si_units:
|
| 150 |
+
solution += (
|
| 151 |
+
f"We convert the length from meters to millimeters to match the elongation units.\n"
|
| 152 |
+
f"delta = {elongation} mm\n"
|
| 153 |
+
f"L = {length} m = {length * 1000} mm\n"
|
| 154 |
+
f"epsilon = ({elongation} mm) / ({length * 1000} mm) = {strain:.3e}\n\n"
|
| 155 |
+
)
|
| 156 |
+
else: # US units
|
| 157 |
+
solution += (
|
| 158 |
+
f"Both elongation and length are already in inches, so we can divide directly.\n"
|
| 159 |
+
f"delta = {elongation} in\n"
|
| 160 |
+
f"L = {length} in\n"
|
| 161 |
+
f"epsilon = ({elongation} in) / ({length} in) = {strain:.3e}\n\n"
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
solution += (
|
| 165 |
+
f"**Answer:**\n"
|
| 166 |
+
f"a) Normal Stress (sigma) = **{round(stress, precision)} {stress_unit}**\n"
|
| 167 |
+
f"b) Normal Strain (epsilon) = **{strain:.3e}** ({strain_unit_explanation})"
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
return question, solution
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# Template 2 (Easy)
|
| 174 |
+
def template_axial_deformation():
|
| 175 |
+
"""
|
| 176 |
+
Axial Deformation (Hooke's Law)
|
| 177 |
+
|
| 178 |
+
Scenario:
|
| 179 |
+
This template combines stress, strain, and the material's Modulus of
|
| 180 |
+
Elasticity (E). It tests the ability to calculate the total elongation
|
| 181 |
+
or compression of a rod under an axial load. It can also be inverted
|
| 182 |
+
to find the maximum load for a given allowable deformation.
|
| 183 |
+
|
| 184 |
+
Core Equation:
|
| 185 |
+
Deformation: delta = (P * L) / (A * E)
|
| 186 |
+
|
| 187 |
+
Returns:
|
| 188 |
+
tuple: A tuple containing:
|
| 189 |
+
- str: A question about axial deformation or allowable load.
|
| 190 |
+
- str: A step-by-step solution showing the calculations.
|
| 191 |
+
"""
|
| 192 |
+
# 1. Parameterize inputs with random values
|
| 193 |
+
use_si_units = random.choice([True, False])
|
| 194 |
+
shape = random.choice(['circular', 'square'])
|
| 195 |
+
material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
|
| 196 |
+
solve_for_load = random.choice([True, False]) # The variation
|
| 197 |
+
precision = 3
|
| 198 |
+
|
| 199 |
+
if use_si_units:
|
| 200 |
+
# --- SI Unit System ---
|
| 201 |
+
material_E = MATERIAL_PROPERTIES[material_name]['E_GPa'] # in GPa
|
| 202 |
+
length = round(random.uniform(0.5, 5.0), 2) # in m
|
| 203 |
+
|
| 204 |
+
if shape == 'circular':
|
| 205 |
+
dimension_val = random.randint(25, 120) # diameter in mm
|
| 206 |
+
dimension_name = "diameter"
|
| 207 |
+
area = math.pi * (dimension_val / 2)**2
|
| 208 |
+
dim_str = f"{dimension_val} mm"
|
| 209 |
+
else: # square
|
| 210 |
+
dimension_val = random.randint(25, 120) # side in mm
|
| 211 |
+
dimension_name = "side length"
|
| 212 |
+
area = dimension_val**2
|
| 213 |
+
dim_str = f"{dimension_val} mm"
|
| 214 |
+
|
| 215 |
+
E_str = f"{material_E} GPa"
|
| 216 |
+
length_str = f"{length} m"
|
| 217 |
+
|
| 218 |
+
if solve_for_load:
|
| 219 |
+
# We are solving for P given delta_max
|
| 220 |
+
max_elongation = round(random.uniform(1.0, 6.0), 2) # in mm
|
| 221 |
+
max_elongation_str = f"{max_elongation} mm"
|
| 222 |
+
# Core Calculation: P = (delta * A * E) / L
|
| 223 |
+
# Units: mm * mm^2 * (N/mm^2) / mm = N
|
| 224 |
+
load_N = (max_elongation * area * (material_E * 1000)) / (length * 1000)
|
| 225 |
+
load = load_N / 1000 # convert to kN
|
| 226 |
+
load_str = f"{round(load, precision)} kN"
|
| 227 |
+
else:
|
| 228 |
+
# We are solving for delta given P
|
| 229 |
+
load = random.randint(50, 800) # in kN
|
| 230 |
+
load_str = f"{load} kN"
|
| 231 |
+
# Core Calculation: delta = (P * L) / (A * E)
|
| 232 |
+
# Units: (N * mm) / (mm^2 * N/mm^2) = mm
|
| 233 |
+
elongation = ((load * 1000) * (length * 1000)) / (area * (material_E * 1000))
|
| 234 |
+
elongation_str = f"{round(elongation, precision)} mm"
|
| 235 |
+
|
| 236 |
+
else:
|
| 237 |
+
# --- US Customary Unit System ---
|
| 238 |
+
material_E = MATERIAL_PROPERTIES[material_name]['E_ksi'] # in ksi
|
| 239 |
+
length = round(random.uniform(12.0, 150.0), 1) # in inches
|
| 240 |
+
|
| 241 |
+
if shape == 'circular':
|
| 242 |
+
dimension_val = round(random.uniform(0.5, 6.0), 2) # diameter in inches
|
| 243 |
+
dimension_name = "diameter"
|
| 244 |
+
area = math.pi * (dimension_val / 2)**2
|
| 245 |
+
dim_str = f"{dimension_val} in"
|
| 246 |
+
else: # square
|
| 247 |
+
dimension_val = round(random.uniform(0.5, 6.0), 2) # side in inches
|
| 248 |
+
dimension_name = "side length"
|
| 249 |
+
area = dimension_val**2
|
| 250 |
+
dim_str = f"{dimension_val} in"
|
| 251 |
+
|
| 252 |
+
E_str = f"{material_E} ksi"
|
| 253 |
+
length_str = f"{length} in"
|
| 254 |
+
|
| 255 |
+
if solve_for_load:
|
| 256 |
+
# Solving for P given delta_max
|
| 257 |
+
max_elongation = round(random.uniform(0.02, 0.2), 4) # in inches
|
| 258 |
+
max_elongation_str = f"{max_elongation} in"
|
| 259 |
+
# Core Calculation: P = (delta * A * E) / L
|
| 260 |
+
# Units: in * in^2 * (kips/in^2) / in = kips
|
| 261 |
+
load = (max_elongation * area * material_E) / length
|
| 262 |
+
load_str = f"{round(load, precision)} kips"
|
| 263 |
+
else:
|
| 264 |
+
# Solving for delta given P
|
| 265 |
+
load = random.randint(10, 200) # in kips
|
| 266 |
+
load_str = f"{load} kips"
|
| 267 |
+
# Core Calculation: delta = (P * L) / (A * E)
|
| 268 |
+
# Units: (kips * in) / (in^2 * kips/in^2) = in
|
| 269 |
+
elongation = (load * length) / (area * material_E)
|
| 270 |
+
elongation_str = f"{round(elongation, precision)} in"
|
| 271 |
+
|
| 272 |
+
# 2. Generate the question and solution strings
|
| 273 |
+
if solve_for_load:
|
| 274 |
+
question = (
|
| 275 |
+
f"A rod made of {material_name} has a length of {length_str} and a {shape} "
|
| 276 |
+
f"cross-section with a {dimension_name} of {dim_str}. The modulus of "
|
| 277 |
+
f"elasticity for {material_name} is {E_str}.\n\n"
|
| 278 |
+
f"What is the maximum allowable axial load the rod can support if the "
|
| 279 |
+
f"total elongation is not to exceed {max_elongation_str}?"
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
solution = (
|
| 283 |
+
f"**Given:**\n"
|
| 284 |
+
f"Material: {material_name} (E = {E_str})\n"
|
| 285 |
+
f"Length (L): {length_str}\n"
|
| 286 |
+
f"Cross-Section: {shape.capitalize()}, {dimension_name} = {dim_str}\n"
|
| 287 |
+
f"Maximum Elongation (delta_max): {max_elongation_str}\n\n"
|
| 288 |
+
|
| 289 |
+
f"**Step 1:** Calculate the Cross-Sectional Area (A)\n"
|
| 290 |
+
)
|
| 291 |
+
if shape == 'circular':
|
| 292 |
+
solution += (f" A = pi * (d/2)^2 = pi * ({dimension_val}/2)^2 = {round(area, precision+1)} {'mm^2' if use_si_units else 'in^2'}\n\n")
|
| 293 |
+
else: # square
|
| 294 |
+
solution += (f" A = side^2 = ({dimension_val})^2 = {round(area, precision+1)} {'mm^2' if use_si_units else 'in^2'}\n\n")
|
| 295 |
+
|
| 296 |
+
solution += (
|
| 297 |
+
f"**Step 2:** Calculate the Allowable Load (P)\n"
|
| 298 |
+
f"The formula for axial deformation is delta = (P * L) / (A * E).\n"
|
| 299 |
+
f"We can rearrange this to solve for the load P: P = (delta * A * E) / L.\n\n"
|
| 300 |
+
f"**Unit Conversion and Calculation:**\n"
|
| 301 |
+
)
|
| 302 |
+
if use_si_units:
|
| 303 |
+
solution += (
|
| 304 |
+
f"For consistency, we will use units of Newtons (N) and millimeters (mm).\n"
|
| 305 |
+
f"delta = {max_elongation} mm\n"
|
| 306 |
+
f"A = {round(area, precision+1)} mm^2\n"
|
| 307 |
+
f"E = {material_E} GPa = {material_E * 1000} MPa = {material_E * 1000} N/mm^2\n"
|
| 308 |
+
f"L = {length} m = {length * 1000} mm\n\n"
|
| 309 |
+
f"P = ({max_elongation} * {round(area, precision+1)} * {material_E * 1000}) / {length * 1000}\n"
|
| 310 |
+
f"P = {round(load_N, precision)} N = {round(load, precision)} kN\n\n"
|
| 311 |
+
)
|
| 312 |
+
else: # US units
|
| 313 |
+
solution += (
|
| 314 |
+
f"The units are already consistent (kips and inches).\n"
|
| 315 |
+
f"delta = {max_elongation} in\n"
|
| 316 |
+
f"A = {round(area, precision+1)} in^2\n"
|
| 317 |
+
f"E = {material_E} ksi\n"
|
| 318 |
+
f"L = {length} in\n\n"
|
| 319 |
+
f"P = ({max_elongation} * {round(area, precision+1)} * {material_E}) / {length}\n"
|
| 320 |
+
f"P = {round(load, precision)} kips\n\n"
|
| 321 |
+
)
|
| 322 |
+
solution += f"**Answer:**\n The maximum allowable load is **{load_str}**."
|
| 323 |
+
else: # solve_for_deformation
|
| 324 |
+
question = (
|
| 325 |
+
f"A {material_name} rod with a length of {length_str} is subjected to an axial "
|
| 326 |
+
f"tensile load of {load_str}. The rod has a {shape} cross-section with a "
|
| 327 |
+
f"{dimension_name} of {dim_str}. The modulus of elasticity for "
|
| 328 |
+
f"{material_name} is {E_str}.\n\n"
|
| 329 |
+
f"Calculate the total elongation of the rod."
|
| 330 |
+
)
|
| 331 |
+
solution = (
|
| 332 |
+
f"**Given:**\n"
|
| 333 |
+
f"Material: {material_name} (E = {E_str})\n"
|
| 334 |
+
f"Load (P): {load_str}\n"
|
| 335 |
+
f"Length (L): {length_str}\n"
|
| 336 |
+
f"Cross-Section: {shape.capitalize()}, {dimension_name} = {dim_str}\n\n"
|
| 337 |
+
|
| 338 |
+
f"**Step 1:** Calculate the Cross-Sectional Area (A)\n"
|
| 339 |
+
)
|
| 340 |
+
if shape == 'circular':
|
| 341 |
+
solution += (f" A = pi * (d/2)^2 = pi * ({dimension_val}/2)^2 = {round(area, precision+1)} {'mm^2' if use_si_units else 'in^2'}\n\n")
|
| 342 |
+
else: # square
|
| 343 |
+
solution += (f" A = side^2 = ({dimension_val})^2 = {round(area, precision+1)} {'mm^2' if use_si_units else 'in^2'}\n\n")
|
| 344 |
+
|
| 345 |
+
solution += (
|
| 346 |
+
f"**Step 2:** Calculate the Elongation (delta)\n"
|
| 347 |
+
f"The formula for axial deformation is delta = (P * L) / (A * E).\n\n"
|
| 348 |
+
f"**Unit Conversion and Calculation:**\n"
|
| 349 |
+
)
|
| 350 |
+
if use_si_units:
|
| 351 |
+
solution += (
|
| 352 |
+
f"For consistency, we will use units of Newtons (N) and millimeters (mm).\n"
|
| 353 |
+
f"P = {load} kN = {load * 1000} N\n"
|
| 354 |
+
f"L = {length} m = {length * 1000} mm\n"
|
| 355 |
+
f"A = {round(area, precision+1)} mm^2\n"
|
| 356 |
+
f"E = {material_E} GPa = {material_E * 1000} MPa = {material_E * 1000} N/mm^2\n\n"
|
| 357 |
+
f"delta = (({load * 1000}) * ({length * 1000})) / (({round(area, precision+1)}) * ({material_E * 1000}))\n"
|
| 358 |
+
f"delta = {round(elongation, precision)} mm\n\n"
|
| 359 |
+
)
|
| 360 |
+
else: # US units
|
| 361 |
+
solution += (
|
| 362 |
+
f"The units are already consistent (kips and inches).\n"
|
| 363 |
+
f"P = {load} kips\n"
|
| 364 |
+
f"L = {length} in\n"
|
| 365 |
+
f"A = {round(area, precision+1)} in^2\n"
|
| 366 |
+
f"E = {material_E} ksi\n\n"
|
| 367 |
+
f"delta = ({load} * {length}) / ({round(area, precision+1)} * {material_E})\n"
|
| 368 |
+
f"delta = {round(elongation, precision)} in\n\n"
|
| 369 |
+
)
|
| 370 |
+
solution += f"**Answer:**\n The total elongation of the rod is **{elongation_str}**."
|
| 371 |
+
|
| 372 |
+
return question, solution
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
# Template 3 (Intermediate)
|
| 376 |
+
def template_multi_segment_rod():
|
| 377 |
+
"""
|
| 378 |
+
Deformation of a Multi-Segment Rod
|
| 379 |
+
|
| 380 |
+
Scenario:
|
| 381 |
+
This template involves a composite rod made of segments joined end-to-end and
|
| 382 |
+
fixed at one end. The segments can have different materials, lengths, or
|
| 383 |
+
cross-sectional areas. External loads are applied at the junctions. The user
|
| 384 |
+
must calculate the total deformation by summing the deformations of each segment,
|
| 385 |
+
which requires first finding the internal force in each section.
|
| 386 |
+
|
| 387 |
+
Core Equation:
|
| 388 |
+
Total Deformation: delta_total = sum(delta_i) = sum( (P_i * L_i) / (A_i * E_i) )
|
| 389 |
+
|
| 390 |
+
Returns:
|
| 391 |
+
tuple: A tuple containing:
|
| 392 |
+
- str: A question about the total deformation of a composite rod.
|
| 393 |
+
- str: A step-by-step solution.
|
| 394 |
+
"""
|
| 395 |
+
# 1. Parameterize inputs
|
| 396 |
+
use_si_units = random.choice([True, False])
|
| 397 |
+
num_segments = random.choice([2, 3])
|
| 398 |
+
precision = 4
|
| 399 |
+
|
| 400 |
+
segments = []
|
| 401 |
+
loads = {} # Loads applied at nodes {node_index: load_value}
|
| 402 |
+
|
| 403 |
+
# Generate properties for each segment
|
| 404 |
+
for i in range(num_segments):
|
| 405 |
+
material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
|
| 406 |
+
segment = {'material_name': material_name}
|
| 407 |
+
|
| 408 |
+
if use_si_units:
|
| 409 |
+
segment['length'] = round(random.uniform(0.2, 1.5), 2) # meters
|
| 410 |
+
# For simplicity, all segments are circular
|
| 411 |
+
diameter = round(random.uniform(20, 75), 1) # mm
|
| 412 |
+
segment['area'] = math.pi * (diameter / 2)**2
|
| 413 |
+
segment['E'] = MATERIAL_PROPERTIES[material_name]['E_GPa']
|
| 414 |
+
segment['dim_str'] = f"{diameter} mm diameter"
|
| 415 |
+
# Node i+1 is the right end of segment i
|
| 416 |
+
loads[i+1] = random.randint(-250, 250) # kN
|
| 417 |
+
else:
|
| 418 |
+
segment['length'] = round(random.uniform(10.0, 60.0), 1) # inches
|
| 419 |
+
diameter = round(random.uniform(0.75, 3.0), 1) # inches
|
| 420 |
+
segment['area'] = math.pi * (diameter / 2)**2
|
| 421 |
+
segment['E'] = MATERIAL_PROPERTIES[material_name]['E_ksi']
|
| 422 |
+
segment['dim_str'] = f"{diameter} in diameter"
|
| 423 |
+
loads[i+1] = random.randint(-60, 60) # kips
|
| 424 |
+
|
| 425 |
+
segments.append(segment)
|
| 426 |
+
|
| 427 |
+
# 2. Perform Core Calculations
|
| 428 |
+
# A. Calculate internal forces (P_i) in each segment
|
| 429 |
+
# We work from the free end (right) to the fixed end (left)
|
| 430 |
+
total_deformation = 0
|
| 431 |
+
cumulative_load = 0
|
| 432 |
+
# Iterate backwards from the last segment to the first
|
| 433 |
+
for i in range(num_segments - 1, -1, -1):
|
| 434 |
+
# The internal force in segment 'i' is the sum of all external loads to its right
|
| 435 |
+
node_index = i + 1
|
| 436 |
+
cumulative_load += loads[node_index]
|
| 437 |
+
segments[i]['internal_load'] = cumulative_load
|
| 438 |
+
|
| 439 |
+
# B. Calculate deformation (delta_i) for each segment and sum them
|
| 440 |
+
for i in range(num_segments):
|
| 441 |
+
P = segments[i]['internal_load']
|
| 442 |
+
L = segments[i]['length']
|
| 443 |
+
A = segments[i]['area']
|
| 444 |
+
E = segments[i]['E']
|
| 445 |
+
|
| 446 |
+
if use_si_units:
|
| 447 |
+
# Convert units for consistency: N, mm, MPa (N/mm^2)
|
| 448 |
+
# P: kN → N (multiply by 1000)
|
| 449 |
+
# L: m → mm (multiply by 1000)
|
| 450 |
+
# A: already in mm²
|
| 451 |
+
# E: GPa → MPa (multiply by 1000)
|
| 452 |
+
# delta = (P_N * L_mm) / (A_mm2 * E_MPa)
|
| 453 |
+
delta = (P * 1000 * L * 1000) / (A * E * 1000) # mm
|
| 454 |
+
else:
|
| 455 |
+
# Units are already consistent: kips, inches, ksi (kip/in^2)
|
| 456 |
+
delta = (P * L) / (A * E) # inches
|
| 457 |
+
|
| 458 |
+
segments[i]['deformation'] = delta
|
| 459 |
+
total_deformation += delta
|
| 460 |
+
|
| 461 |
+
# 3. Generate Question and Solution Strings
|
| 462 |
+
question = (
|
| 463 |
+
f"A composite rod, fixed at one end, consists of {num_segments} segments "
|
| 464 |
+
f"joined end-to-end as described below:\n"
|
| 465 |
+
)
|
| 466 |
+
for i, seg in enumerate(segments):
|
| 467 |
+
unit_L = "m" if use_si_units else "in"
|
| 468 |
+
question += (
|
| 469 |
+
f" - Segment {i+1}: Made of {seg['material_name']}, has a length of {seg['length']} {unit_L}, "
|
| 470 |
+
f"and a cross-section that is {seg['dim_str']}.\n"
|
| 471 |
+
)
|
| 472 |
+
|
| 473 |
+
question += f"\nThe rod is subjected to the following external axial loads (positive = tension, negative = compression):\n"
|
| 474 |
+
for i in range(num_segments):
|
| 475 |
+
node_index = i + 1
|
| 476 |
+
load_val = loads[node_index]
|
| 477 |
+
unit_P = "kN" if use_si_units else "kips"
|
| 478 |
+
|
| 479 |
+
if node_index < num_segments:
|
| 480 |
+
location_desc = f"at the junction between Segment {node_index} and {node_index+1}"
|
| 481 |
+
else:
|
| 482 |
+
location_desc = "at the free end"
|
| 483 |
+
|
| 484 |
+
question += f" - A load of {load_val} {unit_P} is applied {location_desc}.\n"
|
| 485 |
+
|
| 486 |
+
question += (
|
| 487 |
+
f"\nGiven the Moduli of Elasticity:\n"
|
| 488 |
+
)
|
| 489 |
+
unique_materials = {s['material_name'] for s in segments}
|
| 490 |
+
for mat in sorted(list(unique_materials)):
|
| 491 |
+
E_val = MATERIAL_PROPERTIES[mat]['E_GPa' if use_si_units else 'E_ksi']
|
| 492 |
+
unit_E = "GPa" if use_si_units else "ksi"
|
| 493 |
+
question += f" - E_{mat} = {E_val} {unit_E}\n"
|
| 494 |
+
|
| 495 |
+
question += f"\nDetermine the total deformation of the composite rod."
|
| 496 |
+
|
| 497 |
+
# Solution
|
| 498 |
+
solution = f"**Given:** The properties and loads as described in the question.\n\n"
|
| 499 |
+
solution += (
|
| 500 |
+
f"**Step 1:** Calculate the Internal Force (P) in Each Segment\n"
|
| 501 |
+
f"To find the internal force in any segment, we make a virtual 'cut' and sum all external forces acting on one side of the cut. We will work from the free end (right) to the fixed wall (left). A positive force indicates tension, and a negative force indicates compression.\n"
|
| 502 |
+
)
|
| 503 |
+
unit_P = "kN" if use_si_units else "kips"
|
| 504 |
+
for i in range(num_segments - 1, -1, -1):
|
| 505 |
+
seg_num = i + 1
|
| 506 |
+
internal_load = segments[i]['internal_load']
|
| 507 |
+
load_desc = " (Tension)" if internal_load > 0 else " (Compression)" if internal_load < 0 else " (No force)"
|
| 508 |
+
|
| 509 |
+
if seg_num == num_segments: # Last segment
|
| 510 |
+
solution += f" - **Segment {seg_num}:** P{seg_num} = {loads[seg_num]} {unit_P}{load_desc}\n"
|
| 511 |
+
else:
|
| 512 |
+
prev_load_desc = " (T)" if segments[i+1]['internal_load'] > 0 else " (C)" if segments[i+1]['internal_load'] < 0 else " (0)"
|
| 513 |
+
solution += f" - **Segment {seg_num}:** P{seg_num} = P{seg_num+1} + {loads[seg_num]} = {segments[i+1]['internal_load']}{prev_load_desc} + {loads[seg_num]} = {internal_load} {unit_P}{load_desc}\n"
|
| 514 |
+
|
| 515 |
+
solution += f"\nSummary of Internal Forces:\n"
|
| 516 |
+
for i, seg in enumerate(segments):
|
| 517 |
+
load_desc = " (Tension)" if seg['internal_load'] > 0 else " (Compression)"
|
| 518 |
+
solution += f" - P{i+1} = {seg['internal_load']} {unit_P}{load_desc}\n"
|
| 519 |
+
|
| 520 |
+
solution += (
|
| 521 |
+
f"\n**Step 2:** Calculate the Deformation (δ) of Each Segment\n"
|
| 522 |
+
f"Using the formula δ = (P × L) / (A × E) for each segment.\n"
|
| 523 |
+
)
|
| 524 |
+
unit_L = "m" if use_si_units else "in"
|
| 525 |
+
unit_D = "mm" if use_si_units else "in"
|
| 526 |
+
unit_E = "GPa" if use_si_units else "ksi"
|
| 527 |
+
|
| 528 |
+
for i, seg in enumerate(segments):
|
| 529 |
+
solution += f" - **Segment {i+1} ({seg['material_name']}):**\n"
|
| 530 |
+
solution += (
|
| 531 |
+
f"P{i+1} = {seg['internal_load']} {unit_P}\n"
|
| 532 |
+
f"L{i+1} = {seg['length']} {unit_L}\n"
|
| 533 |
+
f"A{i+1} = {round(seg['area'], precision)} {'mm²' if use_si_units else 'in²'}\n"
|
| 534 |
+
f"E{i+1} = {seg['E']} {unit_E}\n"
|
| 535 |
+
f"δ{i+1} = ({seg['internal_load']} × {seg['length']}) / ({round(seg['area'], precision)} × {seg['E']}) = {round(seg['deformation'], precision)} {unit_D}\n"
|
| 536 |
+
)
|
| 537 |
+
if use_si_units:
|
| 538 |
+
solution += f"*Note:* Calculation uses consistent units (N, mm, MPa).\n"
|
| 539 |
+
|
| 540 |
+
solution += (
|
| 541 |
+
f"\n**Step 3:** Calculate the Total Deformation\n"
|
| 542 |
+
f"The total deformation is the algebraic sum of the individual segment deformations.\n"
|
| 543 |
+
f"δ_total = "
|
| 544 |
+
)
|
| 545 |
+
delta_sum_str = " + ".join([f"({round(s['deformation'], precision)})" for s in segments])
|
| 546 |
+
solution += delta_sum_str + "\n"
|
| 547 |
+
solution += f"δ_total = {round(total_deformation, precision)} {unit_D}\n\n"
|
| 548 |
+
|
| 549 |
+
final_desc = "elongation" if total_deformation > 0 else "contraction"
|
| 550 |
+
solution += (
|
| 551 |
+
f"**Answer:**\n"
|
| 552 |
+
f"The total deformation of the rod is **{round(total_deformation, precision)} {unit_D}** "
|
| 553 |
+
f"(a net {final_desc})."
|
| 554 |
+
)
|
| 555 |
+
|
| 556 |
+
return question, solution
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
# Template 4 (Intermediate)
|
| 560 |
+
def template_poissons_ratio():
|
| 561 |
+
"""
|
| 562 |
+
Poisson's Ratio and Change in Diameter
|
| 563 |
+
|
| 564 |
+
Scenario:
|
| 565 |
+
This template tests the understanding of Poisson's Ratio (nu). A rod is
|
| 566 |
+
subjected to an axial load, causing it to elongate and contract laterally.
|
| 567 |
+
The user must calculate the change in the rod's diameter.
|
| 568 |
+
|
| 569 |
+
Core Equations:
|
| 570 |
+
Axial Strain: epsilon_axial = sigma_x / E = P / (A * E)
|
| 571 |
+
Lateral Strain: epsilon_lateral = -nu * epsilon_axial
|
| 572 |
+
Change in Diameter: delta_d = d_initial * epsilon_lateral
|
| 573 |
+
|
| 574 |
+
Returns:
|
| 575 |
+
tuple: A tuple containing:
|
| 576 |
+
- str: A question asking for the change in diameter.
|
| 577 |
+
- str: A step-by-step solution.
|
| 578 |
+
"""
|
| 579 |
+
# 1. Parameterize inputs
|
| 580 |
+
use_si_units = random.choice([True, False])
|
| 581 |
+
material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
|
| 582 |
+
material = MATERIAL_PROPERTIES[material_name]
|
| 583 |
+
precision = 5 # Use higher precision for small strain values
|
| 584 |
+
|
| 585 |
+
# For this problem, the cross-section is always circular
|
| 586 |
+
if use_si_units:
|
| 587 |
+
# SI Unit System
|
| 588 |
+
load_kN = random.randint(50, 950) * random.choice([-1, 1]) # in kN (tensile or compressive)
|
| 589 |
+
initial_length_m = round(random.uniform(0.5, 3.0), 2) # in meters
|
| 590 |
+
initial_diameter_mm = random.randint(25, 125) # in mm
|
| 591 |
+
|
| 592 |
+
area_mm2 = math.pi * (initial_diameter_mm / 2)**2
|
| 593 |
+
E_GPa = material['E_GPa']
|
| 594 |
+
nu = material['nu']
|
| 595 |
+
|
| 596 |
+
# Core Calculations (Units: N, mm, MPa)
|
| 597 |
+
stress_MPa = (load_kN * 1000) / area_mm2
|
| 598 |
+
axial_strain = stress_MPa / (E_GPa * 1000)
|
| 599 |
+
lateral_strain = -nu * axial_strain
|
| 600 |
+
delta_diameter_mm = initial_diameter_mm * lateral_strain
|
| 601 |
+
final_diameter_mm = initial_diameter_mm + delta_diameter_mm
|
| 602 |
+
|
| 603 |
+
# Formatting for question and solution
|
| 604 |
+
load_str = f"{load_kN} kN"
|
| 605 |
+
load_type_str = "tension" if load_kN > 0 else "compression"
|
| 606 |
+
dim_str = f"{initial_diameter_mm} mm"
|
| 607 |
+
unit_d = "mm"
|
| 608 |
+
|
| 609 |
+
else:
|
| 610 |
+
# US Customary Unit System
|
| 611 |
+
load_kips = random.randint(15, 250) * random.choice([-1, 1]) # in kips
|
| 612 |
+
initial_length_in = round(random.uniform(20.0, 100.0), 1) # in inches
|
| 613 |
+
initial_diameter_in = round(random.uniform(1.0, 5.0), 2) # in inches
|
| 614 |
+
|
| 615 |
+
area_in2 = math.pi * (initial_diameter_in / 2)**2
|
| 616 |
+
E_ksi = material['E_ksi']
|
| 617 |
+
nu = material['nu']
|
| 618 |
+
|
| 619 |
+
# Core Calculations (Units: kips, in, ksi)
|
| 620 |
+
stress_ksi = load_kips / area_in2
|
| 621 |
+
axial_strain = stress_ksi / E_ksi
|
| 622 |
+
lateral_strain = -nu * axial_strain
|
| 623 |
+
delta_diameter_in = initial_diameter_in * lateral_strain
|
| 624 |
+
final_diameter_in = initial_diameter_in + delta_diameter_in
|
| 625 |
+
|
| 626 |
+
# Formatting for question and solution
|
| 627 |
+
load_str = f"{load_kips} kips"
|
| 628 |
+
load_type_str = "tension" if load_kips > 0 else "compression"
|
| 629 |
+
dim_str = f"{initial_diameter_in} in"
|
| 630 |
+
unit_d = "in"
|
| 631 |
+
|
| 632 |
+
# 2. Generate Question and Solution Strings
|
| 633 |
+
question = (
|
| 634 |
+
f"A solid circular rod made of {material_name} has an initial diameter of {dim_str}. "
|
| 635 |
+
f"The rod is subjected to an axial load of {load_str} ({load_type_str}).\n\n"
|
| 636 |
+
f"Given the material properties for {material_name}:\n"
|
| 637 |
+
f"Modulus of Elasticity (E) = {material['E_GPa' if use_si_units else 'E_ksi']} {'GPa' if use_si_units else 'ksi'}\n"
|
| 638 |
+
f"Poisson's Ratio (nu) = {material['nu']}\n\n"
|
| 639 |
+
f"Determine the following:\n"
|
| 640 |
+
f"a) The change in the rod's diameter.\n"
|
| 641 |
+
f"b) The final diameter of the rod under the load."
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
solution = (
|
| 645 |
+
f"**Given:**\n"
|
| 646 |
+
f"Material: {material_name} (E = {material['E_GPa' if use_si_units else 'E_ksi']} {'GPa' if use_si_units else 'ksi'}, nu = {nu})\n"
|
| 647 |
+
f"Initial Diameter (d_0): {dim_str}\n"
|
| 648 |
+
f"Axial Load (P): {load_str}\n\n"
|
| 649 |
+
|
| 650 |
+
f"**Step 1:** Calculate Axial Strain (epsilon_axial)\n"
|
| 651 |
+
f"First, we need the cross-sectional area (A) and the axial stress (sigma).\n"
|
| 652 |
+
f"A = pi * (d_0 / 2)^2 = pi * ({initial_diameter_mm if use_si_units else initial_diameter_in} / 2)^2 = {round(area_mm2 if use_si_units else area_in2, 4)} {'mm^2' if use_si_units else 'in^2'}\n"
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
+
if use_si_units:
|
| 656 |
+
solution += (
|
| 657 |
+
f"sigma = P / A = ({load_kN} * 1000 N) / ({round(area_mm2, 4)} mm^2) = {round(stress_MPa, 3)} MPa\n"
|
| 658 |
+
f"Now, calculate axial strain using Hooke's Law: epsilon_axial = sigma / E.\n"
|
| 659 |
+
f"E = {E_GPa} GPa = {E_GPa * 1000} MPa\n"
|
| 660 |
+
f"epsilon_axial = {round(stress_MPa, 3)} MPa / {E_GPa * 1000} MPa = {axial_strain:.4e}\n\n"
|
| 661 |
+
)
|
| 662 |
+
else: # US units
|
| 663 |
+
solution += (
|
| 664 |
+
f"sigma = P / A = {load_kips} kips / {round(area_in2, 4)} in^2 = {round(stress_ksi, 3)} ksi\n"
|
| 665 |
+
f"Now, calculate axial strain using Hooke's Law: epsilon_axial = sigma / E.\n"
|
| 666 |
+
f"epsilon_axial = {round(stress_ksi, 3)} ksi / {E_ksi} ksi = {axial_strain:.4e}\n\n"
|
| 667 |
+
)
|
| 668 |
+
|
| 669 |
+
solution += (
|
| 670 |
+
f"**Step 2:** Calculate Lateral Strain (epsilon_lateral)\n"
|
| 671 |
+
f"Lateral strain is related to axial strain by Poisson's ratio: epsilon_lateral = -nu * epsilon_axial.\n"
|
| 672 |
+
f"The negative sign indicates that for positive axial strain (elongation), the lateral strain is negative (contraction), and vice-versa.\n"
|
| 673 |
+
f"epsilon_lateral = -({nu}) * ({axial_strain:.4e}) = {lateral_strain:.4e}\n\n"
|
| 674 |
+
|
| 675 |
+
f"**Step 3:** Calculate the Change in Diameter (delta_d)\n"
|
| 676 |
+
f"The change in diameter is the lateral strain multiplied by the initial diameter.\n"
|
| 677 |
+
f"delta_d = d_0 * epsilon_lateral\n"
|
| 678 |
+
f"delta_d = ({initial_diameter_mm if use_si_units else initial_diameter_in} {unit_d}) * ({lateral_strain:.4e}) = {round(delta_diameter_mm if use_si_units else delta_diameter_in, precision)} {unit_d}\n\n"
|
| 679 |
+
|
| 680 |
+
f"**Step 4:** Calculate the Final Diameter (d_final)\n"
|
| 681 |
+
f"The final diameter is the initial diameter plus the change.\n"
|
| 682 |
+
f"d_final = d_0 + delta_d\n"
|
| 683 |
+
f"d_final = ({initial_diameter_mm if use_si_units else initial_diameter_in} {unit_d}) + ({round(delta_diameter_mm if use_si_units else delta_diameter_in, precision)} {unit_d}) = {round(final_diameter_mm if use_si_units else final_diameter_in, precision)} {unit_d}\n\n"
|
| 684 |
+
|
| 685 |
+
f"**Answer:**\n"
|
| 686 |
+
f"a) The change in diameter is **{round(delta_diameter_mm if use_si_units else delta_diameter_in, precision)} {unit_d}**.\n"
|
| 687 |
+
f"b) The final diameter is **{round(final_diameter_mm if use_si_units else final_diameter_in, precision)} {unit_d}**."
|
| 688 |
+
)
|
| 689 |
+
|
| 690 |
+
return question, solution
|
| 691 |
+
|
| 692 |
+
|
| 693 |
+
# Template 5 (Advanced)
|
| 694 |
+
def template_statically_indeterminate():
|
| 695 |
+
"""
|
| 696 |
+
Statically Indeterminate Axially Loaded Member
|
| 697 |
+
|
| 698 |
+
Scenario:
|
| 699 |
+
A uniform rod is fixed between two rigid walls. An external load is applied
|
| 700 |
+
at an intermediate point. This is statically indeterminate because there are
|
| 701 |
+
two unknown reaction forces (at the walls) but only one static equilibrium
|
| 702 |
+
equation. The problem is solved by using a compatibility equation based on
|
| 703 |
+
the fact that the total deformation of the rod must be zero.
|
| 704 |
+
|
| 705 |
+
Core Equations:
|
| 706 |
+
Equilibrium: Sum(F_x) = 0 => R_A + R_C = P
|
| 707 |
+
Compatibility: delta_total = 0 => delta_AB + delta_BC = 0
|
| 708 |
+
(P_AB * L_AB / AE) + (P_BC * L_BC / AE) = 0
|
| 709 |
+
|
| 710 |
+
Returns:
|
| 711 |
+
tuple: A tuple containing:
|
| 712 |
+
- str: A question asking for reaction forces and stresses.
|
| 713 |
+
- str: A step-by-step solution.
|
| 714 |
+
"""
|
| 715 |
+
# 1. Parameterize inputs
|
| 716 |
+
use_si_units = random.choice([True, False])
|
| 717 |
+
material_name = random.choice(list(MATERIAL_PROPERTIES.keys()))
|
| 718 |
+
material = MATERIAL_PROPERTIES[material_name]
|
| 719 |
+
shape = random.choice(['circular', 'square'])
|
| 720 |
+
precision = 3
|
| 721 |
+
|
| 722 |
+
if use_si_units:
|
| 723 |
+
# --- SI Unit System ---
|
| 724 |
+
total_length = round(random.uniform(1.0, 3.0), 2) # m
|
| 725 |
+
len_AB = round(random.uniform(0.2 * total_length, 0.8 * total_length), 2) # m
|
| 726 |
+
len_BC = total_length - len_AB
|
| 727 |
+
load_P = random.randint(100, 500) # kN
|
| 728 |
+
|
| 729 |
+
if shape == 'circular':
|
| 730 |
+
diameter = random.randint(50, 150) # mm
|
| 731 |
+
area = math.pi * (diameter / 2)**2
|
| 732 |
+
dim_str = f"a diameter of {diameter} mm"
|
| 733 |
+
else: # square
|
| 734 |
+
side = random.randint(50, 150) # mm
|
| 735 |
+
area = side**2
|
| 736 |
+
dim_str = f"a side length of {side} mm"
|
| 737 |
+
|
| 738 |
+
E_val = material['E_GPa']
|
| 739 |
+
unit_L, unit_P, unit_S, unit_A, unit_E = "m", "kN", "MPa", "mm^2", "GPa"
|
| 740 |
+
|
| 741 |
+
else:
|
| 742 |
+
# --- US Customary Unit System ---
|
| 743 |
+
total_length = round(random.uniform(40.0, 120.0), 1) # inches
|
| 744 |
+
len_AB = round(random.uniform(0.2 * total_length, 0.8 * total_length), 1) # inches
|
| 745 |
+
len_BC = total_length - len_AB
|
| 746 |
+
load_P = random.randint(50, 250) # kips
|
| 747 |
+
|
| 748 |
+
if shape == 'circular':
|
| 749 |
+
diameter = round(random.uniform(2.0, 6.0), 2) # inches
|
| 750 |
+
area = math.pi * (diameter / 2)**2
|
| 751 |
+
dim_str = f"a diameter of {diameter} in"
|
| 752 |
+
else: # square
|
| 753 |
+
side = round(random.uniform(2.0, 6.0), 2) # inches
|
| 754 |
+
area = side**2
|
| 755 |
+
dim_str = f"a side length of {side} in"
|
| 756 |
+
|
| 757 |
+
E_val = material['E_ksi']
|
| 758 |
+
unit_L, unit_P, unit_S, unit_A, unit_E = "in", "kips", "ksi", "in^2", "ksi"
|
| 759 |
+
|
| 760 |
+
# 2. Core Calculations
|
| 761 |
+
# From compatibility: R_A = P * L_BC / L_AC
|
| 762 |
+
# From equilibrium: R_C = P - R_A
|
| 763 |
+
R_A = load_P * (len_BC / total_length)
|
| 764 |
+
R_C = load_P - R_A
|
| 765 |
+
|
| 766 |
+
# Internal forces
|
| 767 |
+
P_AB = R_A # Tension
|
| 768 |
+
P_BC = -R_C # Compression
|
| 769 |
+
|
| 770 |
+
# Stresses
|
| 771 |
+
stress_AB = P_AB / area
|
| 772 |
+
stress_BC = P_BC / area
|
| 773 |
+
|
| 774 |
+
# Unit conversion for SI stress
|
| 775 |
+
if use_si_units:
|
| 776 |
+
stress_AB_MPa = stress_AB * 1000 # Convert kN/mm^2 to MPa
|
| 777 |
+
stress_BC_MPa = stress_BC * 1000 # Convert kN/mm^2 to MPa
|
| 778 |
+
|
| 779 |
+
# 3. Generate Question and Solution Strings
|
| 780 |
+
question = (
|
| 781 |
+
f"A solid {material_name} rod with a uniform {shape} cross-section is fixed at both ends, A and C. "
|
| 782 |
+
f"The rod has a total length of {total_length} {unit_L} and {dim_str}.\n\n"
|
| 783 |
+
f"An external axial load of {load_P} {unit_P} is applied to the right at point B, "
|
| 784 |
+
f"which is located at a distance of {len_AB} {unit_L} from end A.\n\n"
|
| 785 |
+
f"The Modulus of Elasticity for {material_name} is {E_val} {unit_E}.\n\n"
|
| 786 |
+
f"Determine the following:\n"
|
| 787 |
+
f"a) The reaction forces at supports A and C.\n"
|
| 788 |
+
f"b) The normal stress in segments AB and BC of the rod."
|
| 789 |
+
)
|
| 790 |
+
|
| 791 |
+
solution = (
|
| 792 |
+
f"**Given:**\n"
|
| 793 |
+
f"Total Length (L_AC): {total_length} {unit_L}\n"
|
| 794 |
+
f"Length of segment AB (L_AB): {len_AB} {unit_L}\n"
|
| 795 |
+
f"Length of segment BC (L_BC): {total_length} - {len_AB} = {round(len_BC, 2)} {unit_L}\n"
|
| 796 |
+
f"Applied Load (P): {load_P} {unit_P}\n"
|
| 797 |
+
f"Area (A): {round(area, 2)} {unit_A}\n"
|
| 798 |
+
f"Modulus of Elasticity (E): {E_val} {unit_E}\n\n"
|
| 799 |
+
|
| 800 |
+
f"This is a statically indeterminate problem because there are two unknown support reactions (R_A and R_C) and only one equation of static equilibrium.\n\n"
|
| 801 |
+
|
| 802 |
+
f"**Step 1:** Equation of Equilibrium\n"
|
| 803 |
+
f"Summing forces in the x-direction (assuming right is positive):\n"
|
| 804 |
+
f"Sum(F_x) = 0 => -R_A + P - R_C = 0\n"
|
| 805 |
+
f"R_A + R_C = {load_P} {unit_P} ----(Equation 1)\n\n"
|
| 806 |
+
|
| 807 |
+
f"**Step 2:** Equation of Compatibility\n"
|
| 808 |
+
f"Since the rod is fixed between unyielding supports, the total deformation must be zero.\n"
|
| 809 |
+
f"delta_total = delta_AB + delta_BC = 0\n"
|
| 810 |
+
f"The internal force in segment AB is the reaction R_A (in tension, P_AB = R_A).\n"
|
| 811 |
+
f"The internal force in segment BC is the reaction R_C (in compression, P_BC = -R_C).\n\n"
|
| 812 |
+
f"Using the deformation formula delta = PL/AE:\n"
|
| 813 |
+
f"(R_A * L_AB) / (A * E) - (R_C * L_BC) / (A * E) = 0\n"
|
| 814 |
+
f"Since A and E are constant, they cancel out:\n"
|
| 815 |
+
f"R_A * L_AB = R_C * L_BC ----(Equation 2)\n\n"
|
| 816 |
+
|
| 817 |
+
f"**Step 3:** Solve for the Reaction Forces\n"
|
| 818 |
+
f"From Equation 1, we can express R_C as: R_C = {load_P} - R_A.\n"
|
| 819 |
+
f"Substitute this into the simplified Equation 2:\n"
|
| 820 |
+
f"R_A * ({len_AB}) = ({load_P} - R_A) * ({round(len_BC, 2)})\n"
|
| 821 |
+
f"{round(len_AB, 2)}*R_A = {round(load_P * len_BC, 2)} - {round(len_BC, 2)}*R_A\n"
|
| 822 |
+
f"({round(len_AB, 2)} + {round(len_BC, 2)})*R_A = {round(load_P * len_BC, 2)}\n"
|
| 823 |
+
f"({total_length})*R_A = {round(load_P * len_BC, 2)}\n"
|
| 824 |
+
f"R_A = {round(load_P * len_BC, 2)} / {total_length} = {round(R_A, precision)} {unit_P}\n\n"
|
| 825 |
+
f"Now, find R_C using Equation 1:\n"
|
| 826 |
+
f"R_C = {load_P} - R_A = {load_P} - {round(R_A, precision)} = {round(R_C, precision)} {unit_P}\n\n"
|
| 827 |
+
|
| 828 |
+
f"**Step 4:** Calculate the Normal Stresses\n"
|
| 829 |
+
f"The stress in each segment is sigma = P_internal / A.\n"
|
| 830 |
+
f"**Segment AB:** The internal force is P_AB = R_A = {round(R_A, precision)} {unit_P} (Tension).\n"
|
| 831 |
+
)
|
| 832 |
+
if use_si_units:
|
| 833 |
+
solution += f"sigma_AB = ({round(R_A, precision)} kN) / ({round(area, 2)} mm^2) * 1000 = {round(stress_AB_MPa, precision)} MPa (Tension)\n"
|
| 834 |
+
else:
|
| 835 |
+
solution += f"sigma_AB = {round(R_A, precision)} kips / {round(area, 2)} in^2 = {round(stress_AB, precision)} ksi (Tension)\n"
|
| 836 |
+
|
| 837 |
+
solution += f" - **Segment BC:** The internal force is P_BC = -R_C = -{round(R_C, precision)} {unit_P} (Compression).\n"
|
| 838 |
+
|
| 839 |
+
if use_si_units:
|
| 840 |
+
solution += f"sigma_BC = (-{round(R_C, precision)} kN) / ({round(area, 2)} mm^2) * 1000 = {round(stress_BC_MPa, precision)} MPa (Compression)\n\n"
|
| 841 |
+
else:
|
| 842 |
+
solution += f"sigma_BC = -{round(R_C, precision)} kips / {round(area, 2)} in^2 = {round(stress_BC, precision)} ksi (Compression)\n\n"
|
| 843 |
+
|
| 844 |
+
solution += (
|
| 845 |
+
f"**Answer:**\n"
|
| 846 |
+
f"a) Reaction Forces: R_A = **{round(R_A, precision)} {unit_P}** and R_C = **{round(R_C, precision)} {unit_P}**.\n"
|
| 847 |
+
f"b) Normal Stresses: sigma_AB = **{round(stress_AB_MPa if use_si_units else stress_AB, precision)} {unit_S} (Tension)** and sigma_BC = **{round(abs(stress_BC_MPa if use_si_units else stress_BC), precision)} {unit_S} (Compression)**."
|
| 848 |
+
)
|
| 849 |
+
return question, solution
|
| 850 |
+
|
| 851 |
+
|
| 852 |
+
def main():
|
| 853 |
+
"""
|
| 854 |
+
Generate numerous instances of each stres and strain - axial loading template
|
| 855 |
+
with different random seeds and write the results to a JSONL file.
|
| 856 |
+
"""
|
| 857 |
+
import json
|
| 858 |
+
import os
|
| 859 |
+
|
| 860 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 861 |
+
output_file = "testset/mechanical_engineering/mechanics_of_materials/stress_and_strain.jsonl"
|
| 862 |
+
|
| 863 |
+
# Create the directory if it doesn't exist
|
| 864 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 865 |
+
|
| 866 |
+
# List of template functions with their ID and level
|
| 867 |
+
templates = [
|
| 868 |
+
(template_basic_stress_strain, "basic_stress_strain", "Easy"),
|
| 869 |
+
(template_axial_deformation, "axial_deformation", "Easy"),
|
| 870 |
+
(template_multi_segment_rod, "multi_segment_rod", "Intermediate"),
|
| 871 |
+
(template_poissons_ratio, "poissons_ratio", "Intermediate"),
|
| 872 |
+
(template_statically_indeterminate, "statically_indeterminate", "Advanced"),
|
| 873 |
+
]
|
| 874 |
+
|
| 875 |
+
# List to store all generated problems
|
| 876 |
+
all_problems = []
|
| 877 |
+
|
| 878 |
+
# Generate problems for each template
|
| 879 |
+
for template_func, id_name, level in templates:
|
| 880 |
+
for _ in range(50):
|
| 881 |
+
# Generate a unique seed for each problem
|
| 882 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 883 |
+
random.seed(seed)
|
| 884 |
+
|
| 885 |
+
# Generate the problem and solution
|
| 886 |
+
question, solution = template_func()
|
| 887 |
+
|
| 888 |
+
# Create a JSON entry
|
| 889 |
+
problem_entry = {
|
| 890 |
+
"seed": seed,
|
| 891 |
+
"branch": "mechanical_engineering",
|
| 892 |
+
"domain": "mechanics_of_materials",
|
| 893 |
+
"area": "stress_and_strain",
|
| 894 |
+
"id": id_name,
|
| 895 |
+
"level": level,
|
| 896 |
+
"question": question,
|
| 897 |
+
"solution": solution
|
| 898 |
+
}
|
| 899 |
+
|
| 900 |
+
# Add to the list of problems
|
| 901 |
+
all_problems.append(problem_entry)
|
| 902 |
+
|
| 903 |
+
# Shuffle the problems to mix templates and levels
|
| 904 |
+
random.shuffle(all_problems)
|
| 905 |
+
|
| 906 |
+
# Write all problems to a .jsonl file
|
| 907 |
+
with open(output_file, "w") as file:
|
| 908 |
+
for problem in all_problems:
|
| 909 |
+
file.write(json.dumps(problem))
|
| 910 |
+
file.write("\n")
|
| 911 |
+
|
| 912 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 913 |
+
|
| 914 |
+
|
| 915 |
+
if __name__ == "__main__":
|
| 916 |
+
main()
|
data/templates/branches/mechanical_engineering/mechanics_of_materials/torsion.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
from data.templates.branches.mechanical_engineering.constants import SHEAR_MODULUS_VALUES
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_shear_stress_torsion():
|
| 7 |
+
"""
|
| 8 |
+
Torsion: Shear Stress and Polar Moment of Inertia
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template generates a foundational problem testing the ability to calculate
|
| 12 |
+
the polar moment of inertia (J) for a solid or hollow circular shaft.
|
| 13 |
+
It then uses this value to determine the maximum shearing stress (tau_max)
|
| 14 |
+
resulting from an applied torque.
|
| 15 |
+
|
| 16 |
+
Core Equations:
|
| 17 |
+
tau_max = (T * c) / J
|
| 18 |
+
J_solid = (pi / 2) * c^4
|
| 19 |
+
J_hollow = (pi / 2) * (c_outer^4 - c_inner^4)
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
tuple: A tuple containing:
|
| 23 |
+
- str: A question asking for the maximum shearing stress in a shaft.
|
| 24 |
+
- str: A step-by-step solution to the problem.
|
| 25 |
+
"""
|
| 26 |
+
# 1. Parameterize the inputs with random values
|
| 27 |
+
torque = round(random.uniform(100.0, 5000.0), 2) # Torque in N.m
|
| 28 |
+
shaft_type = random.choice(['solid', 'hollow'])
|
| 29 |
+
|
| 30 |
+
# Ensure diameters are distinct and practical
|
| 31 |
+
d_outer = random.randint(30, 150) # Outer diameter in mm
|
| 32 |
+
|
| 33 |
+
# For hollow shafts, ensure the inner diameter is smaller than the outer
|
| 34 |
+
if shaft_type == 'hollow':
|
| 35 |
+
# Ensure inner diameter is at least 10mm smaller to be meaningful
|
| 36 |
+
max_inner_dia = max(20, d_outer - 10)
|
| 37 |
+
d_inner = random.randint(20, max_inner_dia)
|
| 38 |
+
else:
|
| 39 |
+
d_inner = 0
|
| 40 |
+
|
| 41 |
+
# Standardize precision for all calculations and outputs
|
| 42 |
+
precision = 3
|
| 43 |
+
|
| 44 |
+
# 2. Perform the core calculations for the solution
|
| 45 |
+
|
| 46 |
+
# Step A: Convert diameters to radii in meters
|
| 47 |
+
c_outer = d_outer / 2000.0 # Convert mm to m and get radius
|
| 48 |
+
if shaft_type == 'hollow':
|
| 49 |
+
c_inner = d_inner / 2000.0
|
| 50 |
+
|
| 51 |
+
# Step B: Calculate the polar moment of inertia (J)
|
| 52 |
+
if shaft_type == 'solid':
|
| 53 |
+
polar_moment_J = (math.pi / 2) * (c_outer ** 4)
|
| 54 |
+
j_calculation_str = f"J = (pi / 2) * c^4 = (pi / 2) * ({c_outer})^4 = {polar_moment_J:.3e} m^4"
|
| 55 |
+
else: # shaft_type == 'hollow'
|
| 56 |
+
polar_moment_J = (math.pi / 2) * (c_outer ** 4 - c_inner ** 4)
|
| 57 |
+
j_calculation_str = (
|
| 58 |
+
f"J = (pi / 2) * (c_outer^4 - c_inner^4)\n"
|
| 59 |
+
f" J = (pi / 2) * (({c_outer})^4 - ({c_inner})^4) = {polar_moment_J:.3e} m^4"
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
# Step C: Apply the torsion formula to find the stress in Pascals
|
| 63 |
+
# Note: 'c' in the formula refers to the outermost radius, c_outer.
|
| 64 |
+
tau_max_pascals = (torque * c_outer) / polar_moment_J
|
| 65 |
+
|
| 66 |
+
# Step D: Convert the final answer to megapascals (MPa)
|
| 67 |
+
tau_max_mpa = tau_max_pascals / 1e6
|
| 68 |
+
|
| 69 |
+
# 3. Generate the question and solution strings
|
| 70 |
+
|
| 71 |
+
# Construct the part of the question describing the geometry
|
| 72 |
+
if shaft_type == 'solid':
|
| 73 |
+
geometry_desc = f"a solid circular shaft with an outer diameter of {d_outer} mm"
|
| 74 |
+
else: # shaft_type == 'hollow'
|
| 75 |
+
geometry_desc = (
|
| 76 |
+
f"a hollow circular shaft with an outer diameter of {d_outer} mm "
|
| 77 |
+
f"and an inner diameter of {d_inner} mm"
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
question = (
|
| 81 |
+
f"A {geometry_desc} is subjected to a torque of {torque} N.m. "
|
| 82 |
+
f"Determine the maximum shearing stress in the shaft."
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
solution = (
|
| 86 |
+
f"**Given:**\n"
|
| 87 |
+
f"Torque (T) = {torque} N.m\n"
|
| 88 |
+
f"Shaft Type = {shaft_type.capitalize()}\n"
|
| 89 |
+
f"Outer Diameter (d_outer) = {d_outer} mm\n"
|
| 90 |
+
)
|
| 91 |
+
if shaft_type == 'hollow':
|
| 92 |
+
solution += f" - Inner Diameter (d_inner) = {d_inner} mm\n\n"
|
| 93 |
+
else:
|
| 94 |
+
solution += "\n"
|
| 95 |
+
|
| 96 |
+
solution += (
|
| 97 |
+
f"**Step 1:** Convert diameters to radii and express in meters.\n"
|
| 98 |
+
f"The maximum stress occurs at the outer surface, so we use the outer radius (c) for the stress calculation.\n"
|
| 99 |
+
f"Outer radius (c) = d_outer / 2 = {d_outer} / 2 = {d_outer/2.0} mm = {c_outer} m\n"
|
| 100 |
+
)
|
| 101 |
+
if shaft_type == 'hollow':
|
| 102 |
+
solution += f" - Inner radius (c_inner) = d_inner / 2 = {d_inner} / 2 = {d_inner/2.0} mm = {c_inner} m\n"
|
| 103 |
+
solution += "\n"
|
| 104 |
+
|
| 105 |
+
solution += (
|
| 106 |
+
f"**Step 2:** Calculate the polar moment of inertia (J) for the shaft's cross-section.\n"
|
| 107 |
+
f"For a {shaft_type} shaft:\n"
|
| 108 |
+
f" {j_calculation_str}\n\n"
|
| 109 |
+
|
| 110 |
+
f"**Step 3:** Apply the torsion formula to find the maximum shearing stress (tau_max).\n"
|
| 111 |
+
f"The formula is: tau_max = (T * c) / J\n"
|
| 112 |
+
f"T = {torque} N.m\n"
|
| 113 |
+
f"c = {c_outer} m\n"
|
| 114 |
+
f"J = {polar_moment_J:.3e} m^4\n"
|
| 115 |
+
f"tau_max = ({torque} * {c_outer}) / {polar_moment_J:.3e}\n"
|
| 116 |
+
f"tau_max = {tau_max_pascals:.3e} Pa\n\n"
|
| 117 |
+
|
| 118 |
+
f"**Step 4:** Convert the stress from Pascals (Pa) to Megapascals (MPa).\n"
|
| 119 |
+
f"1 MPa = 1,000,000 Pa\n"
|
| 120 |
+
f"tau_max = {tau_max_pascals:.3e} Pa / 1e6 = {round(tau_max_mpa, precision)} MPa\n\n"
|
| 121 |
+
|
| 122 |
+
f"**Answer:**\n"
|
| 123 |
+
f"The maximum shearing stress in the shaft is {round(tau_max_mpa, precision)} MPa."
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
return question, solution
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# Template 2 (Easy)
|
| 130 |
+
def template_angle_of_twist():
|
| 131 |
+
"""
|
| 132 |
+
Torsion: Angle of Twist Calculation
|
| 133 |
+
|
| 134 |
+
Scenario:
|
| 135 |
+
This template assesses the ability to calculate the total angle of twist (phi)
|
| 136 |
+
for a uniform solid circular shaft. It requires a correct understanding of the
|
| 137 |
+
relationship between torque, length, material properties (Shear Modulus), and
|
| 138 |
+
the shaft's geometry (Polar Moment of Inertia).
|
| 139 |
+
|
| 140 |
+
Core Equations:
|
| 141 |
+
phi = (T * L) / (J * G)
|
| 142 |
+
J_solid = (pi / 2) * c^4
|
| 143 |
+
|
| 144 |
+
Returns:
|
| 145 |
+
tuple: A tuple containing:
|
| 146 |
+
- str: A question asking for the angle of twist in a shaft.
|
| 147 |
+
- str: A step-by-step solution to the problem.
|
| 148 |
+
"""
|
| 149 |
+
# 1. Parameterize the inputs with random values
|
| 150 |
+
torque = round(random.uniform(500.0, 6000.0), 1) # Torque in N.m
|
| 151 |
+
length = round(random.uniform(0.5, 4.0), 2) # Length in m
|
| 152 |
+
diameter = random.randint(25, 100) # Diameter in mm
|
| 153 |
+
|
| 154 |
+
# Randomly select a material and its properties
|
| 155 |
+
material_name, shear_modulus_gpa = random.choice(list(SHEAR_MODULUS_VALUES.items()))
|
| 156 |
+
|
| 157 |
+
# Standardize precision for final outputs
|
| 158 |
+
precision = 4
|
| 159 |
+
|
| 160 |
+
# 2. Perform the core calculations for the solution
|
| 161 |
+
|
| 162 |
+
# Step A: Convert diameter (mm) to radius (m)
|
| 163 |
+
c_radius_m = diameter / 2000.0
|
| 164 |
+
|
| 165 |
+
# Step B: Calculate the polar moment of inertia (J)
|
| 166 |
+
polar_moment_J = (math.pi / 2) * (c_radius_m ** 4)
|
| 167 |
+
|
| 168 |
+
# Step C: Ensure all units are consistent (convert G from GPa to Pa)
|
| 169 |
+
shear_modulus_pa = shear_modulus_gpa * 1e9
|
| 170 |
+
|
| 171 |
+
# Step D: Apply the angle of twist formula to find the angle in radians
|
| 172 |
+
angle_rad = (torque * length) / (polar_moment_J * shear_modulus_pa)
|
| 173 |
+
|
| 174 |
+
# Step E: Convert the result from radians to degrees
|
| 175 |
+
angle_deg = math.degrees(angle_rad)
|
| 176 |
+
|
| 177 |
+
# 3. Generate the question and solution strings
|
| 178 |
+
|
| 179 |
+
question = (
|
| 180 |
+
f"A solid {material_name.lower()} shaft with a diameter of {diameter} mm and a length of {length} m "
|
| 181 |
+
f"is subjected to a torque of {torque} N.m. "
|
| 182 |
+
f"Given that the shear modulus (G) for {material_name.lower()} is {shear_modulus_gpa} GPa, "
|
| 183 |
+
f"calculate the total angle of twist. Provide the answer in both radians and degrees."
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
solution = (
|
| 187 |
+
f"**Given:**\n"
|
| 188 |
+
f"Torque (T) = {torque} N.m\n"
|
| 189 |
+
f"Length (L) = {length} m\n"
|
| 190 |
+
f"Diameter (d) = {diameter} mm\n"
|
| 191 |
+
f"Material = {material_name}\n"
|
| 192 |
+
f"Shear Modulus (G) = {shear_modulus_gpa} GPa\n\n"
|
| 193 |
+
|
| 194 |
+
f"**Step 1:** Convert the diameter to a radius in meters.\n"
|
| 195 |
+
f"Radius (c) = d / 2 = {diameter} / 2 = {diameter/2.0} mm = {c_radius_m} m\n\n"
|
| 196 |
+
|
| 197 |
+
f"**Step 2:** Calculate the polar moment of inertia (J) for the solid circular shaft.\n"
|
| 198 |
+
f"Formula: J = (pi / 2) * c^4\n"
|
| 199 |
+
f"J = (pi / 2) * ({c_radius_m})^4 = {polar_moment_J:.4e} m^4\n\n"
|
| 200 |
+
|
| 201 |
+
f"**Step 3:** Ensure consistent units for the angle of twist calculation.\n"
|
| 202 |
+
f"The shear modulus must be in Pascals (Pa) to be consistent with N and m.\n"
|
| 203 |
+
f"G = {shear_modulus_gpa} GPa = {shear_modulus_pa:.2e} Pa\n\n"
|
| 204 |
+
|
| 205 |
+
f"**Step 4:** Apply the angle of twist formula to find the angle in radians.\n"
|
| 206 |
+
f"Formula: phi = (T * L) / (J * G)\n"
|
| 207 |
+
f"phi = ({torque} * {length}) / ({polar_moment_J:.4e} * {shear_modulus_pa:.2e})\n"
|
| 208 |
+
f"phi = {round(angle_rad, precision)} radians\n\n"
|
| 209 |
+
|
| 210 |
+
f"**Step 5:** Convert the angle from radians to degrees.\n"
|
| 211 |
+
f"Angle in degrees = Angle in radians * (180 / pi)\n"
|
| 212 |
+
f"Angle = {round(angle_rad, precision)} * (180 / pi) = {round(angle_deg, precision)} degrees\n\n"
|
| 213 |
+
|
| 214 |
+
f"**Answer:**\n"
|
| 215 |
+
f"The total angle of twist is {round(angle_rad, precision)} radians, which is equivalent to "
|
| 216 |
+
f"{round(angle_deg, precision)} degrees."
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
return question, solution
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
# Template 3 (Intermediate)
|
| 223 |
+
def template_shaft_design_power():
|
| 224 |
+
"""
|
| 225 |
+
Torsion: Power Transmission and Shaft Design
|
| 226 |
+
|
| 227 |
+
Scenario:
|
| 228 |
+
This template generates a classic design problem. It tests the ability to
|
| 229 |
+
first determine the torque on a shaft from its power and rotational speed,
|
| 230 |
+
and then use that torque to calculate the minimum required shaft diameter
|
| 231 |
+
based on an allowable shearing stress.
|
| 232 |
+
|
| 233 |
+
Core Equations:
|
| 234 |
+
P = 2 * pi * f * T
|
| 235 |
+
tau_allow = (T * c) / J
|
| 236 |
+
For a solid shaft, this simplifies to: c = ( (2 * T) / (pi * tau) )^(1/3)
|
| 237 |
+
|
| 238 |
+
Returns:
|
| 239 |
+
tuple: A tuple containing:
|
| 240 |
+
- str: A question asking for the minimum shaft diameter.
|
| 241 |
+
- str: A step-by-step solution to the design problem.
|
| 242 |
+
"""
|
| 243 |
+
# 1. Parameterize the inputs with random values
|
| 244 |
+
power_kw = round(random.uniform(10.0, 500.0), 1)
|
| 245 |
+
allowable_stress_mpa = random.randint(40, 120)
|
| 246 |
+
|
| 247 |
+
# Randomly choose to provide frequency in Hz or RPM to add variety
|
| 248 |
+
use_rpm = random.choice([True, False])
|
| 249 |
+
if use_rpm:
|
| 250 |
+
frequency_rpm = random.randint(500, 3000)
|
| 251 |
+
frequency_hz = frequency_rpm / 60.0
|
| 252 |
+
frequency_str = f"{frequency_rpm} RPM"
|
| 253 |
+
else:
|
| 254 |
+
frequency_hz = random.randint(10, 100)
|
| 255 |
+
frequency_rpm = frequency_hz * 60
|
| 256 |
+
frequency_str = f"{frequency_hz} Hz"
|
| 257 |
+
|
| 258 |
+
# Standardize precision for final outputs
|
| 259 |
+
precision = 2
|
| 260 |
+
|
| 261 |
+
# 2. Perform the core calculations for the solution
|
| 262 |
+
|
| 263 |
+
# Step A: Convert primary units to base SI units (W, Pa, Hz)
|
| 264 |
+
power_w = power_kw * 1000
|
| 265 |
+
allowable_stress_pa = allowable_stress_mpa * 1e6
|
| 266 |
+
|
| 267 |
+
# Step B: Calculate the torque (T) on the shaft using the power formula
|
| 268 |
+
# P = 2 * pi * f * T => T = P / (2 * pi * f)
|
| 269 |
+
torque = power_w / (2 * math.pi * frequency_hz)
|
| 270 |
+
|
| 271 |
+
# Step C: Calculate the required radius (c) using the rearranged torsion formula
|
| 272 |
+
# tau = (T*c) / J = (T*c) / (pi/2 * c^4) = 2*T / (pi*c^3)
|
| 273 |
+
# c^3 = (2 * T) / (pi * tau)
|
| 274 |
+
c_cubed = (2 * torque) / (math.pi * allowable_stress_pa)
|
| 275 |
+
c_radius_m = c_cubed ** (1/3)
|
| 276 |
+
|
| 277 |
+
# Step D: Calculate the diameter in meters and then convert to millimeters
|
| 278 |
+
d_diameter_m = c_radius_m * 2
|
| 279 |
+
d_diameter_mm = d_diameter_m * 1000
|
| 280 |
+
|
| 281 |
+
# 3. Generate the question and solution strings
|
| 282 |
+
|
| 283 |
+
question = (
|
| 284 |
+
f"A motor is required to transmit {power_kw} kW of power at a rotational speed of {frequency_str}. "
|
| 285 |
+
f"If the solid circular shaft is to be made from a material with an allowable shearing stress of {allowable_stress_mpa} MPa, "
|
| 286 |
+
f"determine the minimum required diameter for the shaft."
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
solution = (
|
| 290 |
+
f"**Given:**\n"
|
| 291 |
+
f"Power (P) = {power_kw} kW\n"
|
| 292 |
+
f"Rotational Speed = {frequency_str}\n"
|
| 293 |
+
f"Allowable Shearing Stress (tau_allow) = {allowable_stress_mpa} MPa\n\n"
|
| 294 |
+
|
| 295 |
+
f"**Step 1:** Convert the given values to base SI units (Watts, Pascals, Hertz).\n"
|
| 296 |
+
f"Power (P) = {power_kw} kW = {power_w} W\n"
|
| 297 |
+
f"Allowable Stress (tau_allow) = {allowable_stress_mpa} MPa = {allowable_stress_pa:.1e} Pa\n"
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
if use_rpm:
|
| 301 |
+
solution += (
|
| 302 |
+
f" - Frequency (f) = {frequency_rpm} RPM = {frequency_rpm} / 60 = {round(frequency_hz, 2)} Hz\n\n"
|
| 303 |
+
)
|
| 304 |
+
else:
|
| 305 |
+
solution += f" - Frequency (f) = {frequency_hz} Hz\n\n"
|
| 306 |
+
|
| 307 |
+
solution += (
|
| 308 |
+
f"**Step 2:** Calculate the torque (T) exerted on the shaft.\n"
|
| 309 |
+
f"The relationship between power, torque, and frequency is P = 2 * pi * f * T.\n"
|
| 310 |
+
f"Rearranging for torque: T = P / (2 * pi * f)\n"
|
| 311 |
+
f"T = {power_w} / (2 * pi * {round(frequency_hz, 2)}) = {round(torque, 2)} N.m\n\n"
|
| 312 |
+
|
| 313 |
+
f"**Step 3:** Determine the required shaft radius (c) using the torsion formula.\n"
|
| 314 |
+
f"The formula for maximum stress in a solid shaft is tau = (T * c) / J, where J = (pi/2) * c^4.\n"
|
| 315 |
+
f"This simplifies to tau = 2 * T / (pi * c^3).\n"
|
| 316 |
+
f"Rearranging to solve for the radius: c^3 = (2 * T) / (pi * tau_allow)\n"
|
| 317 |
+
f"c^3 = (2 * {round(torque, 2)}) / (pi * {allowable_stress_pa:.1e}) = {c_cubed:.3e} m^3\n"
|
| 318 |
+
f"c = ({c_cubed:.3e})^(1/3) = {round(c_radius_m, 4)} m\n\n"
|
| 319 |
+
|
| 320 |
+
f"**Step 4:** Calculate the minimum diameter from the radius.\n"
|
| 321 |
+
f"Diameter (d) = 2 * c\n"
|
| 322 |
+
f"d = 2 * {round(c_radius_m, 4)} = {round(d_diameter_m, 4)} m\n"
|
| 323 |
+
f"In millimeters, d = {round(d_diameter_mm, precision)} mm\n\n"
|
| 324 |
+
|
| 325 |
+
f"**Answer:**\n"
|
| 326 |
+
f"The minimum required diameter for the solid circular shaft is {round(d_diameter_mm, precision)} mm."
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
return question, solution
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
# Template 4 (Intermediate)
|
| 333 |
+
def template_composite_shafts_series():
|
| 334 |
+
"""
|
| 335 |
+
Torsion: Composite Shafts in Series
|
| 336 |
+
|
| 337 |
+
Scenario:
|
| 338 |
+
This problem involves a shaft made of two different segments joined end-to-end.
|
| 339 |
+
It tests the understanding that the total angle of twist at the free end is
|
| 340 |
+
the algebraic sum of the angles of twist of each individual segment.
|
| 341 |
+
|
| 342 |
+
Core Equations:
|
| 343 |
+
phi_total = sum( (T_i * L_i) / (J_i * G_i) )
|
| 344 |
+
For this case: phi_total = phi_1 + phi_2
|
| 345 |
+
J_solid = (pi / 2) * c^4
|
| 346 |
+
|
| 347 |
+
Returns:
|
| 348 |
+
tuple: A tuple containing:
|
| 349 |
+
- str: A question about a composite shaft in series.
|
| 350 |
+
- str: A step-by-step solution.
|
| 351 |
+
"""
|
| 352 |
+
# 1. Parameterize the inputs with random values
|
| 353 |
+
torque = round(random.uniform(200.0, 7500.0), 1)
|
| 354 |
+
|
| 355 |
+
# Properties for Segment 1 (AB)
|
| 356 |
+
l1 = round(random.uniform(0.5, 2.5), 2)
|
| 357 |
+
d1 = random.randint(40, 120)
|
| 358 |
+
mat1_name, g1_gpa = random.choice(list(SHEAR_MODULUS_VALUES.items()))
|
| 359 |
+
|
| 360 |
+
# Properties for Segment 2 (BC)
|
| 361 |
+
l2 = round(random.uniform(0.5, 2.5), 2)
|
| 362 |
+
d2 = random.randint(30, d1) # Ensure d2 is not larger than d1 for a typical stepped shaft
|
| 363 |
+
mat2_name, g2_gpa = random.choice(list(SHEAR_MODULUS_VALUES.items()))
|
| 364 |
+
|
| 365 |
+
# Standardize precision for final outputs
|
| 366 |
+
precision = 4
|
| 367 |
+
|
| 368 |
+
# 2. Perform the core calculations
|
| 369 |
+
|
| 370 |
+
# --- Calculations for Segment 1 (AB) ---
|
| 371 |
+
c1_m = d1 / 2000.0
|
| 372 |
+
g1_pa = g1_gpa * 1e9
|
| 373 |
+
j1 = (math.pi / 2) * (c1_m ** 4)
|
| 374 |
+
phi1_rad = (torque * l1) / (j1 * g1_pa)
|
| 375 |
+
|
| 376 |
+
# --- Calculations for Segment 2 (BC) ---
|
| 377 |
+
c2_m = d2 / 2000.0
|
| 378 |
+
g2_pa = g2_gpa * 1e9
|
| 379 |
+
j2 = (math.pi / 2) * (c2_m ** 4)
|
| 380 |
+
phi2_rad = (torque * l2) / (j2 * g2_pa)
|
| 381 |
+
|
| 382 |
+
# --- Total Angle of Twist ---
|
| 383 |
+
phi_total_rad = phi1_rad + phi2_rad
|
| 384 |
+
phi_total_deg = math.degrees(phi_total_rad)
|
| 385 |
+
|
| 386 |
+
# 3. Generate the question and solution strings
|
| 387 |
+
|
| 388 |
+
question = (
|
| 389 |
+
f"A composite shaft consists of two segments, AB and BC, rigidly connected at B. "
|
| 390 |
+
f"Segment AB is a solid {mat1_name.lower()} shaft of length {l1} m and diameter {d1} mm. "
|
| 391 |
+
f"Segment BC is a solid {mat2_name.lower()} shaft of length {l2} m and diameter {d2} mm. "
|
| 392 |
+
f"The shaft is fixed at end A, and a torque of {torque} N.m is applied at the free end C. "
|
| 393 |
+
f"Calculate the total angle of twist at end C. "
|
| 394 |
+
f"(Use G = {g1_gpa} GPa for {mat1_name.lower()} and G = {g2_gpa} GPa for {mat2_name.lower()})."
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
solution = (
|
| 398 |
+
f"**Given:**\n"
|
| 399 |
+
f"Applied Torque (T) = {torque} N.m\n"
|
| 400 |
+
f"Segment AB: L1={l1} m, d1={d1} mm, Material={mat1_name} (G1={g1_gpa} GPa)\n"
|
| 401 |
+
f"Segment BC: L2={l2} m, d2={d2} mm, Material={mat2_name} (G2={g2_gpa} GPa)\n\n"
|
| 402 |
+
|
| 403 |
+
f"**Step 1:** Analyze the System\n"
|
| 404 |
+
f"The shaft is fixed at A, so the torque T = {torque} N.m is transmitted through both segments AB and BC. "
|
| 405 |
+
f"The total angle of twist at C is the sum of the twist in segment AB and the twist in segment BC.\n"
|
| 406 |
+
f"phi_total = phi_AB + phi_BC\n\n"
|
| 407 |
+
|
| 408 |
+
f"**Step 2:** Calculate Angle of Twist for Segment AB (phi_AB)\n"
|
| 409 |
+
f"Convert units: c1 = {d1}/2 mm = {c1_m} m; G1 = {g1_gpa} GPa = {g1_pa:.2e} Pa\n"
|
| 410 |
+
f"Polar Moment of Inertia (J1) = (pi/2) * c1^4 = (pi/2) * ({c1_m})^4 = {j1:.3e} m^4\n"
|
| 411 |
+
f"Angle of Twist (phi_AB) = (T * L1) / (J1 * G1)\n"
|
| 412 |
+
f"phi_AB = ({torque} * {l1}) / ({j1:.3e} * {g1_pa:.2e}) = {round(phi1_rad, precision + 1)} radians\n\n"
|
| 413 |
+
|
| 414 |
+
f"**Step 3:** Calculate Angle of Twist for Segment BC (phi_BC)\n"
|
| 415 |
+
f"Convert units: c2 = {d2}/2 mm = {c2_m} m; G2 = {g2_gpa} GPa = {g2_pa:.2e} Pa\n"
|
| 416 |
+
f"Polar Moment of Inertia (J2) = (pi/2) * c2^4 = (pi/2) * ({c2_m})^4 = {j2:.3e} m^4\n"
|
| 417 |
+
f"Angle of Twist (phi_BC) = (T * L2) / (J2 * G2)\n"
|
| 418 |
+
f"phi_BC = ({torque} * {l2}) / ({j2:.3e} * {g2_pa:.2e}) = {round(phi2_rad, precision + 1)} radians\n\n"
|
| 419 |
+
|
| 420 |
+
f"**Step 4:** Calculate Total Angle of Twist at End C\n"
|
| 421 |
+
f"phi_total = phi_AB + phi_BC\n"
|
| 422 |
+
f"phi_total = {round(phi1_rad, precision + 1)} + {round(phi2_rad, precision + 1)} = {round(phi_total_rad, precision)} radians\n"
|
| 423 |
+
f"To convert to degrees: Angle_deg = Angle_rad * (180 / pi)\n"
|
| 424 |
+
f"phi_total = {round(phi_total_rad, precision)} * (180 / pi) = {round(phi_total_deg, precision)} degrees\n\n"
|
| 425 |
+
|
| 426 |
+
f"**Answer:**\n"
|
| 427 |
+
f"The total angle of twist at the free end C is {round(phi_total_rad, precision)} radians, "
|
| 428 |
+
f"or {round(phi_total_deg, precision)} degrees."
|
| 429 |
+
)
|
| 430 |
+
|
| 431 |
+
return question, solution
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
# Template 5 (Advanced)
|
| 435 |
+
def template_statically_indeterminate_shaft():
|
| 436 |
+
"""
|
| 437 |
+
Torsion: Statically Indeterminate Shaft
|
| 438 |
+
|
| 439 |
+
Scenario:
|
| 440 |
+
This problem deals with a shaft that is fixed at both ends and has a torque
|
| 441 |
+
applied at an intermediate point. Since there are two unknown reaction torques
|
| 442 |
+
but only one static equilibrium equation, the problem is statically
|
| 443 |
+
indeterminate. It must be solved by considering both static equilibrium and
|
| 444 |
+
the geometric compatibility of deformation (i.e., the total angle of twist is zero).
|
| 445 |
+
|
| 446 |
+
Core Equations:
|
| 447 |
+
1. Statics: T_A + T_B = T_applied
|
| 448 |
+
2. Compatibility: phi_AC + phi_CB = 0 => (T_A * L_AC) / (JG) = (T_B * L_BC) / (JG)
|
| 449 |
+
This simplifies to: T_A * L_AC = T_B * L_BC
|
| 450 |
+
|
| 451 |
+
Returns:
|
| 452 |
+
tuple: A tuple containing:
|
| 453 |
+
- str: A question asking for the reaction torques.
|
| 454 |
+
- str: A step-by-step solution using statics and compatibility.
|
| 455 |
+
"""
|
| 456 |
+
# 1. Parameterize the inputs with random values
|
| 457 |
+
total_length = round(random.uniform(2.0, 6.0), 2)
|
| 458 |
+
applied_torque = round(random.uniform(1000.0, 15000.0), -2)
|
| 459 |
+
diameter = random.randint(50, 150)
|
| 460 |
+
|
| 461 |
+
# Ensure the torque is applied at a non-trivial location
|
| 462 |
+
pos_L_AC = round(random.uniform(0.2 * total_length, 0.8 * total_length), 2)
|
| 463 |
+
pos_L_BC = total_length - pos_L_AC
|
| 464 |
+
|
| 465 |
+
material_name, shear_modulus_gpa = random.choice(list(SHEAR_MODULUS_VALUES.items()))
|
| 466 |
+
|
| 467 |
+
# Standardize precision for final outputs
|
| 468 |
+
precision = 2
|
| 469 |
+
|
| 470 |
+
# 2. Perform the core calculations
|
| 471 |
+
# Based on the system of equations:
|
| 472 |
+
# (1) T_A + T_B = T_applied
|
| 473 |
+
# (2) T_A * L_AC = T_B * L_BC => T_B = T_A * (L_AC / L_BC)
|
| 474 |
+
# Substitute (2) into (1):
|
| 475 |
+
# T_A + T_A * (L_AC / L_BC) = T_applied
|
| 476 |
+
# T_A * (1 + L_AC/L_BC) = T_applied
|
| 477 |
+
# T_A * ( (L_BC + L_AC) / L_BC ) = T_applied
|
| 478 |
+
# T_A * (L / L_BC) = T_applied
|
| 479 |
+
# T_A = T_applied * (L_BC / L)
|
| 480 |
+
|
| 481 |
+
reaction_torque_A = applied_torque * (pos_L_BC / total_length)
|
| 482 |
+
reaction_torque_B = applied_torque * (pos_L_AC / total_length)
|
| 483 |
+
|
| 484 |
+
# 3. Generate the question and solution strings
|
| 485 |
+
|
| 486 |
+
question = (
|
| 487 |
+
f"A solid {material_name.lower()} shaft of diameter {diameter} mm and length {total_length} m is "
|
| 488 |
+
f"fixed at both ends, A and B. A torque of {int(applied_torque)} N.m is applied at point C, "
|
| 489 |
+
f"located {pos_L_AC} m from end A. The shear modulus for the material is {shear_modulus_gpa} GPa. "
|
| 490 |
+
f"Determine the reaction torques at the fixed supports, T_A and T_B."
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
solution = (
|
| 494 |
+
f"**Given:**\n"
|
| 495 |
+
f"Total Length (L) = {total_length} m\n"
|
| 496 |
+
f"Applied Torque (T_applied) = {int(applied_torque)} N.m\n"
|
| 497 |
+
f"Location of Torque from A (L_AC) = {pos_L_AC} m\n"
|
| 498 |
+
f"Diameter (d) = {diameter} mm, Material = {material_name}\n\n"
|
| 499 |
+
|
| 500 |
+
f"**Analysis:**\n"
|
| 501 |
+
f"This problem is statically indeterminate because there are two unknown reaction torques (T_A and T_B) "
|
| 502 |
+
f"and only one relevant equation from statics. We need an additional equation from the deformation of the shaft.\n\n"
|
| 503 |
+
|
| 504 |
+
f"**Step 1:** Statics Equilibrium Equation\n"
|
| 505 |
+
f"For the shaft to be in rotational equilibrium, the sum of all torques must be zero. Let's assume T_A and T_B act in the opposite direction to T_applied.\n"
|
| 506 |
+
f"(1) T_A + T_B = T_applied = {int(applied_torque)} N.m\n\n"
|
| 507 |
+
|
| 508 |
+
f"**Step 2:** Compatibility Equation\n"
|
| 509 |
+
f"Since the shaft is fixed at both ends, the total angle of twist from A to B must be zero. The twist from A to C and the twist from C to B must cancel each other out.\n"
|
| 510 |
+
f"phi_A_to_B = phi_AC + phi_CB = 0\n"
|
| 511 |
+
f"The torque in section AC is T_A. The torque in section CB is T_applied - T_A = T_B. For our compatibility equation, it is easier to think of the torque in CB as T_B acting from the other direction.\n"
|
| 512 |
+
f"phi_AC = (T_A * L_AC) / (J*G)\n"
|
| 513 |
+
f"phi_CB = -(T_B * L_BC) / (J*G) (The negative sign indicates it twists in the opposite direction)\n"
|
| 514 |
+
f"(T_A * L_AC) / (J*G) - (T_B * L_BC) / (J*G) = 0\n"
|
| 515 |
+
f"The terms J and G are constant for the shaft and cancel out, leaving a relationship between the torques and lengths:\n"
|
| 516 |
+
f"(2) T_A * L_AC = T_B * L_BC\n\n"
|
| 517 |
+
|
| 518 |
+
f"**Step 3:** Solve the System of Two Equations\n"
|
| 519 |
+
f"We have two equations:\n"
|
| 520 |
+
f"(1) T_A + T_B = {int(applied_torque)}\n"
|
| 521 |
+
f"(2) T_A * {pos_L_AC} = T_B * {round(pos_L_BC, 2)}\n"
|
| 522 |
+
f"From equation (2), we can express T_B in terms of T_A:\n"
|
| 523 |
+
f"T_B = T_A * ({pos_L_AC} / {round(pos_L_BC, 2)})\n"
|
| 524 |
+
f"Substitute this into equation (1):\n"
|
| 525 |
+
f"T_A + T_A * ({pos_L_AC} / {round(pos_L_BC, 2)}) = {int(applied_torque)}\n"
|
| 526 |
+
f"T_A * (1 + {round(pos_L_AC / pos_L_BC, 3)}) = {int(applied_torque)}\n"
|
| 527 |
+
f"T_A = {int(applied_torque)} / {round(1 + (pos_L_AC / pos_L_BC), 3)} = {round(reaction_torque_A, precision)} N.m\n"
|
| 528 |
+
f"Now find T_B using equation (1):\n"
|
| 529 |
+
f"T_B = {int(applied_torque)} - T_A = {int(applied_torque)} - {round(reaction_torque_A, precision)} = {round(reaction_torque_B, precision)} N.m\n\n"
|
| 530 |
+
|
| 531 |
+
f"**Answer:**\n"
|
| 532 |
+
f"The reaction torques at the supports are:\n"
|
| 533 |
+
f"T_A = {round(reaction_torque_A, precision)} N.m\n"
|
| 534 |
+
f"T_B = {round(reaction_torque_B, precision)} N.m"
|
| 535 |
+
)
|
| 536 |
+
|
| 537 |
+
return question, solution
|
| 538 |
+
|
| 539 |
+
|
| 540 |
+
def main():
|
| 541 |
+
"""
|
| 542 |
+
Generate numerous instances of each torsion template
|
| 543 |
+
with different random seeds and write the results to a JSONL file.
|
| 544 |
+
"""
|
| 545 |
+
import json
|
| 546 |
+
import os
|
| 547 |
+
|
| 548 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 549 |
+
output_file = "testset/mechanical_engineering/mechanics_of_materials/torsion.jsonl"
|
| 550 |
+
|
| 551 |
+
# Create the directory if it doesn't exist
|
| 552 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 553 |
+
|
| 554 |
+
# List of template functions with their ID and level
|
| 555 |
+
templates = [
|
| 556 |
+
(template_shear_stress_torsion, "shear_stress_torsion", "Easy"),
|
| 557 |
+
(template_angle_of_twist, "angle_of_twist", "Easy"),
|
| 558 |
+
(template_shaft_design_power, "shaft_design_power", "Intermediate"),
|
| 559 |
+
(template_composite_shafts_series, "composite_shafts_series", "Intermediate"),
|
| 560 |
+
(template_statically_indeterminate_shaft, "statically_indeterminate_shaft", "Advanced"),
|
| 561 |
+
]
|
| 562 |
+
|
| 563 |
+
# List to store all generated problems
|
| 564 |
+
all_problems = []
|
| 565 |
+
|
| 566 |
+
# Generate problems for each template
|
| 567 |
+
for template_func, id_name, level in templates:
|
| 568 |
+
for _ in range(50):
|
| 569 |
+
# Generate a unique seed for each problem
|
| 570 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 571 |
+
random.seed(seed)
|
| 572 |
+
|
| 573 |
+
# Generate the problem and solution
|
| 574 |
+
question, solution = template_func()
|
| 575 |
+
|
| 576 |
+
# Create a JSON entry
|
| 577 |
+
problem_entry = {
|
| 578 |
+
"seed": seed,
|
| 579 |
+
"branch": "mechanical_engineering",
|
| 580 |
+
"domain": "mechanics_of_materials",
|
| 581 |
+
"area": "torsion",
|
| 582 |
+
"id": id_name,
|
| 583 |
+
"level": level,
|
| 584 |
+
"question": question,
|
| 585 |
+
"solution": solution
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
# Add to the list of problems
|
| 589 |
+
all_problems.append(problem_entry)
|
| 590 |
+
|
| 591 |
+
# Shuffle the problems to mix templates and levels
|
| 592 |
+
random.shuffle(all_problems)
|
| 593 |
+
|
| 594 |
+
# Write all problems to a .jsonl file
|
| 595 |
+
with open(output_file, "w") as file:
|
| 596 |
+
for problem in all_problems:
|
| 597 |
+
file.write(json.dumps(problem))
|
| 598 |
+
file.write("\n")
|
| 599 |
+
|
| 600 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 601 |
+
|
| 602 |
+
|
| 603 |
+
if __name__ == "__main__":
|
| 604 |
+
main()
|
data/templates/branches/mechanical_engineering/vibrations_and_acoustics/harmonically_excited_vibrations.py
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_system_properties():
|
| 7 |
+
"""
|
| 8 |
+
Vibrations: System Properties (Natural Frequency and Damping Ratio)
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template generates a foundational problem for analyzing a single-degree-of-freedom
|
| 12 |
+
spring-mass-damper system. Given the system's physical parameters (mass, stiffness,
|
| 13 |
+
and damping coefficient), the objective is to calculate its key dynamic properties:
|
| 14 |
+
the undamped natural frequency, the critical damping coefficient, and the damping ratio.
|
| 15 |
+
Finally, the system is classified based on its damping level.
|
| 16 |
+
|
| 17 |
+
Core Equations:
|
| 18 |
+
omega_n = sqrt(k / m)
|
| 19 |
+
c_cr = 2 * sqrt(k * m)
|
| 20 |
+
zeta = c / c_cr
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
tuple: A tuple containing:
|
| 24 |
+
- str: A question asking for the system's dynamic properties and classification.
|
| 25 |
+
- str: A step-by-step solution to the problem.
|
| 26 |
+
"""
|
| 27 |
+
# 1. Parameterize the inputs with random values
|
| 28 |
+
|
| 29 |
+
# Mass in kg, ensuring a practical range
|
| 30 |
+
mass = round(random.uniform(2.0, 250.0), 2)
|
| 31 |
+
|
| 32 |
+
# Stiffness in N/m
|
| 33 |
+
stiffness = round(random.uniform(1000.0, 150000.0), 1)
|
| 34 |
+
|
| 35 |
+
# To generate diverse and meaningful scenarios (underdamped, critically damped, overdamped),
|
| 36 |
+
# we first determine the critical damping and then set the actual damping based on it.
|
| 37 |
+
|
| 38 |
+
# We select a random damping ratio first to define the system type
|
| 39 |
+
target_zeta = round(random.uniform(0.1, 2.5), 3)
|
| 40 |
+
|
| 41 |
+
# Handle the edge case of m=0 or k=0, though randomization range prevents it.
|
| 42 |
+
if mass <= 0 or stiffness <= 0:
|
| 43 |
+
# Assign default safe values in the unlikely event of non-positive inputs
|
| 44 |
+
mass = 10.0
|
| 45 |
+
stiffness = 20000.0
|
| 46 |
+
|
| 47 |
+
critical_damping = 2 * math.sqrt(stiffness * mass)
|
| 48 |
+
|
| 49 |
+
# Calculate the actual damping coefficient based on the target zeta
|
| 50 |
+
damping_coeff = round(target_zeta * critical_damping, 2)
|
| 51 |
+
|
| 52 |
+
# Standardize precision for all calculations and outputs
|
| 53 |
+
precision = 4
|
| 54 |
+
|
| 55 |
+
# 2. Perform the core calculations for the solution
|
| 56 |
+
|
| 57 |
+
# Step A: Calculate the undamped natural frequency (omega_n)
|
| 58 |
+
omega_n = math.sqrt(stiffness / mass)
|
| 59 |
+
|
| 60 |
+
# Step B: The critical damping coefficient (c_cr) was already calculated
|
| 61 |
+
# We re-calculate here to show the step clearly in the solution.
|
| 62 |
+
c_critical = 2 * math.sqrt(stiffness * mass)
|
| 63 |
+
|
| 64 |
+
# Step C: Calculate the damping ratio (zeta)
|
| 65 |
+
damping_ratio = damping_coeff / c_critical
|
| 66 |
+
|
| 67 |
+
# Step D: Classify the system based on the damping ratio
|
| 68 |
+
if abs(damping_ratio - 1.0) < 1e-9: # Use tolerance for floating point comparison
|
| 69 |
+
system_type = "critically damped"
|
| 70 |
+
elif damping_ratio < 1.0:
|
| 71 |
+
system_type = "underdamped"
|
| 72 |
+
else:
|
| 73 |
+
system_type = "overdamped"
|
| 74 |
+
|
| 75 |
+
# 3. Generate the question and solution strings
|
| 76 |
+
|
| 77 |
+
question = (
|
| 78 |
+
f"A spring-mass-damper system has the following properties:\n"
|
| 79 |
+
f"Mass (m) = {mass} kg\n"
|
| 80 |
+
f"Spring Stiffness (k) = {stiffness} N/m\n"
|
| 81 |
+
f"Damping Coefficient (c) = {damping_coeff} N.s/m\n\n"
|
| 82 |
+
f"Determine the following:\n"
|
| 83 |
+
f" 1. The undamped natural frequency (in rad/s).\n"
|
| 84 |
+
f" 2. The critical damping coefficient.\n"
|
| 85 |
+
f" 3. The damping ratio.\n"
|
| 86 |
+
f" 4. Classify the system as underdamped, critically damped, or overdamped."
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
solution = (
|
| 90 |
+
f"**Given:**\n"
|
| 91 |
+
f"Mass (m) = {mass} kg\n"
|
| 92 |
+
f"Stiffness (k) = {stiffness} N/m\n"
|
| 93 |
+
f"Damping Coefficient (c) = {damping_coeff} N.s/m\n\n"
|
| 94 |
+
|
| 95 |
+
f"**Step 1:** Calculate the undamped natural frequency (omega_n).\n"
|
| 96 |
+
f"The formula is: omega_n = sqrt(k / m)\n"
|
| 97 |
+
f"omega_n = sqrt({stiffness} / {mass})\n"
|
| 98 |
+
f"omega_n = {round(omega_n, precision)} rad/s\n\n"
|
| 99 |
+
|
| 100 |
+
f"**Step 2:** Calculate the critical damping coefficient (c_cr).\n"
|
| 101 |
+
f"The formula is: c_cr = 2 * sqrt(k * m)\n"
|
| 102 |
+
f"c_cr = 2 * sqrt({stiffness} * {mass})\n"
|
| 103 |
+
f"c_cr = {round(c_critical, precision)} N.s/m\n\n"
|
| 104 |
+
|
| 105 |
+
f"**Step 3:** Calculate the damping ratio (zeta).\n"
|
| 106 |
+
f"The formula is: zeta = c / c_cr\n"
|
| 107 |
+
f"zeta = {damping_coeff} / {round(c_critical, precision)}\n"
|
| 108 |
+
f"zeta = {round(damping_ratio, precision)}\n\n"
|
| 109 |
+
|
| 110 |
+
f"**Step 4:** Classify the system based on the damping ratio.\n"
|
| 111 |
+
f"The damping ratio is {round(damping_ratio, precision)}.\n"
|
| 112 |
+
f"Since zeta is {'less than 1' if system_type == 'underdamped' else ('equal to 1' if system_type == 'critically damped' else 'greater than 1')}, "
|
| 113 |
+
f"the system is classified as **{system_type}**.\n\n"
|
| 114 |
+
|
| 115 |
+
f"**Answer:**\n"
|
| 116 |
+
f"Undamped Natural Frequency: {round(omega_n, precision)} rad/s\n"
|
| 117 |
+
f"Critical Damping Coefficient: {round(c_critical, precision)} N.s/m\n"
|
| 118 |
+
f"Damping Ratio: {round(damping_ratio, precision)}\n"
|
| 119 |
+
f"System Type: {system_type.capitalize()}"
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
return question, solution
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
# Template 2 (Intermediate)
|
| 126 |
+
def template_rotating_unbalance():
|
| 127 |
+
"""
|
| 128 |
+
Vibrations: Response to Rotating Unbalance
|
| 129 |
+
|
| 130 |
+
Scenario:
|
| 131 |
+
This template generates a problem involving a machine with a rotating component
|
| 132 |
+
that is out of balance. This common engineering scenario creates a harmonic
|
| 133 |
+
excitation force whose magnitude is dependent on the operating speed. The goal
|
| 134 |
+
is to determine the resulting steady-state vibration amplitude.
|
| 135 |
+
|
| 136 |
+
Core Equations:
|
| 137 |
+
omega_n = sqrt(k / m)
|
| 138 |
+
zeta = c / (2 * sqrt(k * m))
|
| 139 |
+
r = omega / omega_n
|
| 140 |
+
F_0 = m_e * e * omega^2
|
| 141 |
+
X = F_0 / sqrt((k - m * omega^2)^2 + (c * omega)^2)
|
| 142 |
+
|
| 143 |
+
Returns:
|
| 144 |
+
tuple: A tuple containing:
|
| 145 |
+
- str: A question asking for the steady-state amplitude of vibration.
|
| 146 |
+
- str: A step-by-step solution to the problem.
|
| 147 |
+
"""
|
| 148 |
+
# 1. Parameterize the inputs with random values
|
| 149 |
+
|
| 150 |
+
# Total mass of the machine in kg
|
| 151 |
+
m_total = round(random.uniform(50.0, 500.0), 1)
|
| 152 |
+
|
| 153 |
+
# Eccentric mass in kg (should be a small fraction of total mass)
|
| 154 |
+
m_eccentric = round(random.uniform(0.1, m_total * 0.05), 2)
|
| 155 |
+
|
| 156 |
+
# Eccentricity in mm
|
| 157 |
+
eccentricity_mm = random.randint(10, 150)
|
| 158 |
+
|
| 159 |
+
# Stiffness in N/m
|
| 160 |
+
stiffness = round(random.uniform(5e4, 2e6), 0)
|
| 161 |
+
|
| 162 |
+
# Define system properties by choosing a damping ratio first for better control
|
| 163 |
+
# Lightly damped systems are common for this problem type.
|
| 164 |
+
damping_ratio_zeta = round(random.uniform(0.05, 0.6), 3)
|
| 165 |
+
|
| 166 |
+
# Define operating speed by choosing a frequency ratio first
|
| 167 |
+
# This ensures we get a good spread of cases (below, near, and above resonance)
|
| 168 |
+
freq_ratio_r = round(random.uniform(0.3, 3.0), 3)
|
| 169 |
+
|
| 170 |
+
# Standardize precision for final outputs
|
| 171 |
+
precision = 5
|
| 172 |
+
|
| 173 |
+
# 2. Perform the core calculations for the solution
|
| 174 |
+
|
| 175 |
+
# Handle potential edge cases from inputs
|
| 176 |
+
if m_total <= 0 or stiffness <= 0:
|
| 177 |
+
return "Error: Mass and stiffness must be positive.", "Invalid input parameters."
|
| 178 |
+
|
| 179 |
+
# Step A: Calculate fundamental system properties
|
| 180 |
+
omega_n = math.sqrt(stiffness / m_total)
|
| 181 |
+
c_critical = 2 * math.sqrt(stiffness * m_total)
|
| 182 |
+
damping_coeff = damping_ratio_zeta * c_critical
|
| 183 |
+
|
| 184 |
+
# Step B: Determine the operating speed from the chosen frequency ratio
|
| 185 |
+
omega = freq_ratio_r * omega_n
|
| 186 |
+
operating_speed_rpm = omega * 60 / (2 * math.pi)
|
| 187 |
+
|
| 188 |
+
# Step C: Convert units for calculation consistency
|
| 189 |
+
eccentricity_m = eccentricity_mm / 1000.0
|
| 190 |
+
|
| 191 |
+
# Step D: Calculate the magnitude of the unbalanced force
|
| 192 |
+
force_magnitude_F0 = m_eccentric * eccentricity_m * (omega ** 2)
|
| 193 |
+
|
| 194 |
+
# Step E: Calculate the steady-state amplitude (X) in meters
|
| 195 |
+
# Denominator components
|
| 196 |
+
term1 = stiffness - m_total * (omega ** 2)
|
| 197 |
+
term2 = damping_coeff * omega
|
| 198 |
+
denominator = math.sqrt(term1**2 + term2**2)
|
| 199 |
+
|
| 200 |
+
amplitude_m = force_magnitude_F0 / denominator if denominator != 0 else float('inf')
|
| 201 |
+
|
| 202 |
+
# Step F: Convert final amplitude to millimeters for a more intuitive answer
|
| 203 |
+
amplitude_mm = amplitude_m * 1000.0
|
| 204 |
+
|
| 205 |
+
# 3. Generate the question and solution strings
|
| 206 |
+
|
| 207 |
+
question = (
|
| 208 |
+
f"A machine with a total mass of {m_total} kg is supported by a spring and damper system. "
|
| 209 |
+
f"The system has an equivalent stiffness of {stiffness:,.0f} N/m and an equivalent damping "
|
| 210 |
+
f"coefficient of {round(damping_coeff, 2)} N.s/m.\n\n"
|
| 211 |
+
f"The machine contains a rotating component that has an unbalance equivalent to a mass of "
|
| 212 |
+
f"{m_eccentric} kg located at an eccentricity of {eccentricity_mm} mm. "
|
| 213 |
+
f"If the machine operates at a speed of {round(operating_speed_rpm, 0):,.0f} RPM, "
|
| 214 |
+
f"determine the steady-state amplitude of vibration."
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
solution = (
|
| 218 |
+
f"**Given:**\n"
|
| 219 |
+
f"Total Mass (m) = {m_total} kg\n"
|
| 220 |
+
f"Stiffness (k) = {stiffness:,.0f} N/m\n"
|
| 221 |
+
f"Damping Coefficient (c) = {round(damping_coeff, 2)} N.s/m\n"
|
| 222 |
+
f"Eccentric Mass (m_e) = {m_eccentric} kg\n"
|
| 223 |
+
f"Eccentricity (e) = {eccentricity_mm} mm = {eccentricity_m} m\n"
|
| 224 |
+
f"Operating Speed = {round(operating_speed_rpm, 0):,.0f} RPM\n\n"
|
| 225 |
+
|
| 226 |
+
f"**Step 1:** Convert the operating speed from RPM to rad/s.\n"
|
| 227 |
+
f"omega = (Speed in RPM) * (2 * pi / 60)\n"
|
| 228 |
+
f"omega = {round(operating_speed_rpm, 0):,.0f} * (2 * pi / 60) = {round(omega, precision)} rad/s\n\n"
|
| 229 |
+
|
| 230 |
+
f"**Step 2:** Calculate the system's natural frequency (omega_n) and damping ratio (zeta).\n"
|
| 231 |
+
f"omega_n = sqrt(k / m) = sqrt({stiffness:,.0f} / {m_total}) = {round(omega_n, precision)} rad/s\n"
|
| 232 |
+
f"c_cr = 2 * sqrt(k * m) = 2 * sqrt({stiffness:,.0f} * {m_total}) = {round(c_critical, precision)} N.s/m\n"
|
| 233 |
+
f"zeta = c / c_cr = {round(damping_coeff, 2)} / {round(c_critical, precision)} = {round(damping_ratio_zeta, precision)}\n\n"
|
| 234 |
+
|
| 235 |
+
f"**Step 3:** Calculate the magnitude of the unbalanced force (F_0).\n"
|
| 236 |
+
f"The force from a rotating unbalance is given by F_0 = m_e * e * omega^2.\n"
|
| 237 |
+
f"F_0 = {m_eccentric} kg * {eccentricity_m} m * ({round(omega, precision)} rad/s)^2\n"
|
| 238 |
+
f"F_0 = {round(force_magnitude_F0, precision)} N\n\n"
|
| 239 |
+
|
| 240 |
+
f"**Step 4:** Calculate the steady-state amplitude of vibration (X).\n"
|
| 241 |
+
f"The formula for amplitude is: X = F_0 / sqrt((k - m * omega^2)^2 + (c * omega)^2)\n"
|
| 242 |
+
f"Numerator = F_0 = {round(force_magnitude_F0, precision)} N\n"
|
| 243 |
+
f"Denominator Part 1: (k - m * omega^2) = ({stiffness:,.0f} - {m_total} * {round(omega, precision)}^2) = {round(term1, precision)}\n"
|
| 244 |
+
f"Denominator Part 2: (c * omega) = ({round(damping_coeff, 2)} * {round(omega, precision)}) = {round(term2, precision)}\n"
|
| 245 |
+
f"Denominator = sqrt(({round(term1, precision)})^2 + ({round(term2, precision)})^2) = {round(denominator, precision)}\n"
|
| 246 |
+
f"X = {round(force_magnitude_F0, precision)} / {round(denominator, precision)}\n"
|
| 247 |
+
f"X = {round(amplitude_m, precision + 2)} m\n\n"
|
| 248 |
+
|
| 249 |
+
f"**Step 5:** Convert the amplitude to millimeters.\n"
|
| 250 |
+
f"Amplitude in mm = {round(amplitude_m, precision + 2)} m * 1000 mm/m = {round(amplitude_mm, precision)} mm\n\n"
|
| 251 |
+
|
| 252 |
+
f"**Answer:**\n"
|
| 253 |
+
f"The steady-state amplitude of vibration is **{round(amplitude_mm, 3)} mm**."
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
return question, solution
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
# Template 3 (Intermediate)
|
| 260 |
+
def template_vibration_transmissibility():
|
| 261 |
+
"""
|
| 262 |
+
Vibrations: Displacement Transmissibility from Base Excitation
|
| 263 |
+
|
| 264 |
+
Scenario:
|
| 265 |
+
This template addresses the problem of a system mounted on a vibrating foundation.
|
| 266 |
+
It is a key concept in vibration isolation, where the goal is to minimize the
|
| 267 |
+
motion transmitted from a vibrating source to a sensitive component. The template
|
| 268 |
+
calculates the ratio of the output amplitude to the input (base) amplitude
|
| 269 |
+
and the absolute amplitude of the system.
|
| 270 |
+
|
| 271 |
+
Core Equations:
|
| 272 |
+
omega_n = sqrt(k / m)
|
| 273 |
+
zeta = c / (2 * sqrt(k * m))
|
| 274 |
+
r = omega / omega_n
|
| 275 |
+
TR = X / Y = sqrt( (1 + (2*zeta*r)^2) / ( (1 - r^2)^2 + (2*zeta*r)^2 ) )
|
| 276 |
+
|
| 277 |
+
Returns:
|
| 278 |
+
tuple: A tuple containing:
|
| 279 |
+
- str: A question asking for the transmissibility and absolute amplitude.
|
| 280 |
+
- str: A step-by-step solution to the problem.
|
| 281 |
+
"""
|
| 282 |
+
# 1. Parameterize the inputs with random values
|
| 283 |
+
|
| 284 |
+
# Mass of the sensitive instrument in kg
|
| 285 |
+
mass = round(random.uniform(5.0, 150.0), 1)
|
| 286 |
+
|
| 287 |
+
# Stiffness of the isolation mount in N/m
|
| 288 |
+
stiffness = round(random.uniform(2e3, 5e5), 0)
|
| 289 |
+
|
| 290 |
+
# Amplitude of the base vibration in mm
|
| 291 |
+
base_amplitude_Y_mm = round(random.uniform(0.1, 8.0), 2)
|
| 292 |
+
|
| 293 |
+
# Use frequency ratio and damping ratio to control the problem's outcome
|
| 294 |
+
# This ensures we test conditions of amplification (r~1) and isolation (r > sqrt(2))
|
| 295 |
+
freq_ratio_r = round(random.uniform(0.2, 5.0), 3)
|
| 296 |
+
damping_ratio_zeta = round(random.uniform(0.05, 0.7), 3)
|
| 297 |
+
|
| 298 |
+
# Standardize precision for final outputs
|
| 299 |
+
precision = 4
|
| 300 |
+
|
| 301 |
+
# 2. Perform the core calculations for the solution
|
| 302 |
+
|
| 303 |
+
# Handle potential edge cases
|
| 304 |
+
if mass <= 0 or stiffness <= 0:
|
| 305 |
+
return "Error: Mass and stiffness must be positive.", "Invalid input parameters."
|
| 306 |
+
|
| 307 |
+
# Step A: Calculate fundamental system properties
|
| 308 |
+
omega_n = math.sqrt(stiffness / mass)
|
| 309 |
+
c_critical = 2 * math.sqrt(stiffness * mass)
|
| 310 |
+
damping_coeff = damping_ratio_zeta * c_critical
|
| 311 |
+
|
| 312 |
+
# Step B: Determine the base excitation frequency from the chosen frequency ratio
|
| 313 |
+
omega = freq_ratio_r * omega_n
|
| 314 |
+
base_freq_hz = omega / (2 * math.pi)
|
| 315 |
+
|
| 316 |
+
# Step C: Convert base amplitude to meters for calculation
|
| 317 |
+
base_amplitude_Y_m = base_amplitude_Y_mm / 1000.0
|
| 318 |
+
|
| 319 |
+
# Step D: Calculate the displacement transmissibility ratio (TR)
|
| 320 |
+
tr_num = 1 + (2 * damping_ratio_zeta * freq_ratio_r)**2
|
| 321 |
+
tr_den = (1 - freq_ratio_r**2)**2 + (2 * damping_ratio_zeta * freq_ratio_r)**2
|
| 322 |
+
|
| 323 |
+
# Avoid division by zero, although highly unlikely with these random ranges
|
| 324 |
+
if tr_den == 0:
|
| 325 |
+
transmissibility_ratio = float('inf')
|
| 326 |
+
else:
|
| 327 |
+
transmissibility_ratio = math.sqrt(tr_num / tr_den)
|
| 328 |
+
|
| 329 |
+
# Step E: Calculate the absolute amplitude of the instrument's vibration
|
| 330 |
+
amplitude_X_m = transmissibility_ratio * base_amplitude_Y_m
|
| 331 |
+
amplitude_X_mm = amplitude_X_m * 1000.0
|
| 332 |
+
|
| 333 |
+
# 3. Generate the question and solution strings
|
| 334 |
+
|
| 335 |
+
question = (
|
| 336 |
+
f"A sensitive instrument of mass {mass} kg is supported by an isolation mount. "
|
| 337 |
+
f"The mount has an effective stiffness of {stiffness:,.0f} N/m and provides a damping "
|
| 338 |
+
f"ratio of {damping_ratio_zeta}.\n\n"
|
| 339 |
+
f"The foundation on which the instrument is placed is vibrating harmonically at a frequency of "
|
| 340 |
+
f"{round(base_freq_hz, 2)} Hz with an amplitude of {base_amplitude_Y_mm} mm.\n\n"
|
| 341 |
+
f"Determine:\n"
|
| 342 |
+
f"1. The displacement transmissibility ratio.\n"
|
| 343 |
+
f"2. The absolute amplitude of vibration of the instrument in millimeters."
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
solution = (
|
| 347 |
+
f"**Given:**\n"
|
| 348 |
+
f"Mass (m) = {mass} kg\n"
|
| 349 |
+
f"Stiffness (k) = {stiffness:,.0f} N/m\n"
|
| 350 |
+
f"Damping Ratio (zeta) = {damping_ratio_zeta}\n"
|
| 351 |
+
f"Base Vibration Frequency (f) = {round(base_freq_hz, 2)} Hz\n"
|
| 352 |
+
f"Base Vibration Amplitude (Y) = {base_amplitude_Y_mm} mm = {base_amplitude_Y_m} m\n\n"
|
| 353 |
+
|
| 354 |
+
f"**Step 1:** Calculate the system's undamped natural frequency (omega_n).\n"
|
| 355 |
+
f"omega_n = sqrt(k / m) = sqrt({stiffness:,.0f} / {mass}) = {round(omega_n, precision)} rad/s\n\n"
|
| 356 |
+
|
| 357 |
+
f"**Step 2:** Convert the base vibration frequency to rad/s and find the frequency ratio (r).\n"
|
| 358 |
+
f"Base frequency (omega) = f * 2 * pi = {round(base_freq_hz, 2)} * 2 * pi = {round(omega, precision)} rad/s\n"
|
| 359 |
+
f"Frequency ratio (r) = omega / omega_n = {round(omega, precision)} / {round(omega_n, precision)} = {round(freq_ratio_r, precision)}\n\n"
|
| 360 |
+
|
| 361 |
+
f"**Step 3:** Calculate the displacement transmissibility ratio (TR).\n"
|
| 362 |
+
f"The formula is: TR = sqrt( (1 + (2*zeta*r)^2) / ( (1 - r^2)^2 + (2*zeta*r)^2 ) )\n"
|
| 363 |
+
f"Let's calculate the terms:\n"
|
| 364 |
+
f"r = {round(freq_ratio_r, precision)}\n"
|
| 365 |
+
f"zeta = {damping_ratio_zeta}\n"
|
| 366 |
+
f"Numerator = 1 + (2 * {damping_ratio_zeta} * {round(freq_ratio_r, precision)})^2 = {round(tr_num, precision)}\n"
|
| 367 |
+
f"Denominator = (1 - ({round(freq_ratio_r, precision)})^2)^2 + (2 * {damping_ratio_zeta} * {round(freq_ratio_r, precision)})^2 = {round(tr_den, precision)}\n"
|
| 368 |
+
f"TR = sqrt({round(tr_num, precision)} / {round(tr_den, precision)}) = {round(transmissibility_ratio, precision)}\n\n"
|
| 369 |
+
|
| 370 |
+
f"**Step 4:** Calculate the absolute amplitude of the instrument (X).\n"
|
| 371 |
+
f"The relationship is X = TR * Y.\n"
|
| 372 |
+
f"X = {round(transmissibility_ratio, precision)} * {base_amplitude_Y_mm} mm\n"
|
| 373 |
+
f"X = {round(amplitude_X_mm, precision)} mm\n\n"
|
| 374 |
+
|
| 375 |
+
f"**Answer:**\n"
|
| 376 |
+
f"The displacement transmissibility ratio is **{round(transmissibility_ratio, 3)}**.\n"
|
| 377 |
+
f"The absolute amplitude of the instrument's vibration is **{round(amplitude_X_mm, 3)} mm**."
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
return question, solution
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
# Template 4 (Advanced)
|
| 384 |
+
def template_vibration_isolator_design():
|
| 385 |
+
"""
|
| 386 |
+
Vibrations: Vibration Isolator Design (Inverse Problem)
|
| 387 |
+
|
| 388 |
+
Scenario:
|
| 389 |
+
This template creates an advanced design problem. An engine generating a harmonic
|
| 390 |
+
force needs to be mounted on isolators to limit the force transmitted to its
|
| 391 |
+
foundation. Given a maximum allowable force transmission percentage, the task
|
| 392 |
+
is to determine the necessary stiffness of the isolation system. This is an
|
| 393 |
+
inverse problem, requiring algebraic manipulation to solve for a system parameter.
|
| 394 |
+
|
| 395 |
+
Core Equations:
|
| 396 |
+
TR = sqrt( (1 + (2*zeta*r)^2) / ( (1 - r^2)^2 + (2*zeta*r)^2 ) )
|
| 397 |
+
This is solved for r, which is then used to find omega_n and finally k.
|
| 398 |
+
k = m * omega_n^2
|
| 399 |
+
|
| 400 |
+
Returns:
|
| 401 |
+
tuple: A tuple containing:
|
| 402 |
+
- str: A question asking for the required stiffness of an isolator.
|
| 403 |
+
- str: A step-by-step solution to the design problem.
|
| 404 |
+
"""
|
| 405 |
+
# 1. Parameterize the inputs with random values
|
| 406 |
+
|
| 407 |
+
# Mass of the engine in kg
|
| 408 |
+
mass = round(random.uniform(100.0, 2000.0), 1)
|
| 409 |
+
|
| 410 |
+
# Operating speed in RPM
|
| 411 |
+
operating_speed_rpm = random.randint(500, 3000)
|
| 412 |
+
|
| 413 |
+
# Desired force transmissibility in percent
|
| 414 |
+
transmissibility_percent = random.randint(5, 20)
|
| 415 |
+
|
| 416 |
+
# Assumed damping ratio for the isolators (typically low)
|
| 417 |
+
damping_ratio_zeta = round(random.uniform(0.05, 0.25), 3)
|
| 418 |
+
|
| 419 |
+
# Standardize precision for final outputs
|
| 420 |
+
precision = 4
|
| 421 |
+
|
| 422 |
+
# 2. Perform the core calculations for the solution
|
| 423 |
+
|
| 424 |
+
# Step A: Convert inputs to consistent units
|
| 425 |
+
omega = operating_speed_rpm * (2 * math.pi / 60)
|
| 426 |
+
transmissibility_ratio_TR = transmissibility_percent / 100.0
|
| 427 |
+
|
| 428 |
+
# Step B: Solve for the frequency ratio (r)
|
| 429 |
+
# The equation TR^2 = (1 + (2*zeta*r)^2) / ((1-r^2)^2 + (2*zeta*r)^2)
|
| 430 |
+
# rearranges into a quadratic equation in terms of r^2: A*(r^2)^2 + B*(r^2) + C = 0
|
| 431 |
+
TR_sq = transmissibility_ratio_TR**2
|
| 432 |
+
|
| 433 |
+
A = TR_sq
|
| 434 |
+
B = 4 * (damping_ratio_zeta**2) * (TR_sq - 1) - 2 * TR_sq
|
| 435 |
+
C = TR_sq - 1
|
| 436 |
+
|
| 437 |
+
# Calculate the discriminant
|
| 438 |
+
discriminant = B**2 - 4 * A * C
|
| 439 |
+
|
| 440 |
+
# Ensure a real solution exists (handles complex roots)
|
| 441 |
+
if discriminant < 0:
|
| 442 |
+
# This case is highly unlikely with TR < 1, but it's good practice to handle it.
|
| 443 |
+
return ("Error: No real solution for frequency ratio (complex roots).",
|
| 444 |
+
"The design parameters are not physically achievable.")
|
| 445 |
+
|
| 446 |
+
# Solve the quadratic equation for r^2
|
| 447 |
+
r_sq_sol1 = (-B + math.sqrt(discriminant)) / (2 * A)
|
| 448 |
+
r_sq_sol2 = (-B - math.sqrt(discriminant)) / (2 * A)
|
| 449 |
+
|
| 450 |
+
# For effective isolation (transmissibility TR < 1), the frequency ratio 'r'
|
| 451 |
+
# must be greater than sqrt(2). We therefore need the larger, positive root for r^2.
|
| 452 |
+
r_squared = max(r_sq_sol1, r_sq_sol2)
|
| 453 |
+
|
| 454 |
+
# Add validation check for non-physical results (negative roots for r^2)
|
| 455 |
+
if r_squared < 0:
|
| 456 |
+
return ("Error: Design parameters result in a non-physical solution (r^2 < 0).",
|
| 457 |
+
"The required transmissibility cannot be achieved with the given damping.")
|
| 458 |
+
|
| 459 |
+
freq_ratio_r = math.sqrt(r_squared)
|
| 460 |
+
|
| 461 |
+
# Step C: Calculate the required natural frequency (omega_n)
|
| 462 |
+
omega_n_req = omega / freq_ratio_r
|
| 463 |
+
|
| 464 |
+
# Step D: Calculate the required stiffness (k)
|
| 465 |
+
stiffness_req = mass * (omega_n_req**2)
|
| 466 |
+
|
| 467 |
+
# 3. Generate the question and solution strings
|
| 468 |
+
|
| 469 |
+
question = (
|
| 470 |
+
f"An engine with a mass of {mass} kg operates at a constant speed of {operating_speed_rpm} RPM. "
|
| 471 |
+
f"It needs to be mounted on a set of vibration isolators. The design specification requires that no more than "
|
| 472 |
+
f"{transmissibility_percent}% of the engine's unbalanced force is transmitted to the foundation.\n\n"
|
| 473 |
+
f"Assuming the isolators have a combined damping ratio of {damping_ratio_zeta}, "
|
| 474 |
+
f"determine the total required stiffness (k) of the isolation system."
|
| 475 |
+
)
|
| 476 |
+
|
| 477 |
+
solution = (
|
| 478 |
+
f"**Given:**\n"
|
| 479 |
+
f"Mass (m) = {mass} kg\n"
|
| 480 |
+
f"Operating Speed = {operating_speed_rpm} RPM\n"
|
| 481 |
+
f"Maximum Transmissibility (TR) = {transmissibility_percent}% = {transmissibility_ratio_TR}\n"
|
| 482 |
+
f"Damping Ratio (zeta) = {damping_ratio_zeta}\n\n"
|
| 483 |
+
|
| 484 |
+
f"**Step 1:** Convert the operating speed to rad/s.\n"
|
| 485 |
+
f"omega = {operating_speed_rpm} RPM * (2 * pi / 60) = {round(omega, precision)} rad/s\n\n"
|
| 486 |
+
|
| 487 |
+
f"**Step 2:** Set up the force transmissibility equation to solve for the frequency ratio (r).\n"
|
| 488 |
+
f"The formula is TR^2 = [1 + (2*zeta*r)^2] / [(1 - r^2)^2 + (2*zeta*r)^2]\n"
|
| 489 |
+
f"Rearranging this gives a quadratic equation in the form A(r^2)^2 + B(r^2) + C = 0.\n"
|
| 490 |
+
f"A = TR^2 = {round(TR_sq, precision)}\n"
|
| 491 |
+
f"B = 4*zeta^2*(TR^2 - 1) - 2*TR^2 = 4*({damping_ratio_zeta}^2)*({round(TR_sq, precision)} - 1) - 2*{round(TR_sq, precision)} = {round(B, precision)}\n"
|
| 492 |
+
f"C = TR^2 - 1 = {round(TR_sq, precision)} - 1 = {round(C, precision)}\n\n"
|
| 493 |
+
|
| 494 |
+
f"**Step 3:** Solve the quadratic equation for r^2 using the formula r^2 = (-B +/- sqrt(B^2 - 4AC)) / 2A.\n"
|
| 495 |
+
f"Discriminant (D) = B^2 - 4AC = ({round(B, precision)})^2 - 4*({round(A, precision)})*({round(C, precision)}) = {round(discriminant, precision)}\n"
|
| 496 |
+
f"The two solutions for r^2 are: {round(r_sq_sol1, precision)} and {round(r_sq_sol2, precision)}.\n"
|
| 497 |
+
f"For effective vibration isolation, the frequency ratio 'r' must be greater than sqrt(2) (approx 1.414). This requires us to select the larger of the two positive solutions for r^2.\n"
|
| 498 |
+
f"Required r^2 = {round(r_squared, precision)}\n\n"
|
| 499 |
+
|
| 500 |
+
f"**Step 4:** Calculate the required frequency ratio (r) and validate the isolation condition.\n"
|
| 501 |
+
f"r = sqrt({round(r_squared, precision)}) = {round(freq_ratio_r, precision)}\n"
|
| 502 |
+
f"Check: Is r > sqrt(2)? Yes, {round(freq_ratio_r, precision)} > 1.414. The condition for isolation is met.\n\n"
|
| 503 |
+
|
| 504 |
+
f"**Step 5:** Determine the required natural frequency (omega_n) of the system.\n"
|
| 505 |
+
f"Since r = omega / omega_n, the required omega_n = omega / r.\n"
|
| 506 |
+
f"omega_n = {round(omega, precision)} / {round(freq_ratio_r, precision)} = {round(omega_n_req, precision)} rad/s\n\n"
|
| 507 |
+
|
| 508 |
+
f"**Step 6:** Calculate the total required stiffness (k).\n"
|
| 509 |
+
f"The natural frequency is defined by omega_n = sqrt(k / m). Therefore, k = m * omega_n^2.\n"
|
| 510 |
+
f"k = {mass} kg * ({round(omega_n_req, precision)} rad/s)^2\n"
|
| 511 |
+
f"k = {round(stiffness_req, 0):,.0f} N/m\n\n"
|
| 512 |
+
|
| 513 |
+
f"**Answer:**\n"
|
| 514 |
+
f"The total required stiffness for the isolation system is **{round(stiffness_req, 0):,.0f} N/m**.\n\n"
|
| 515 |
+
f"*Note: In a practical application, this total stiffness would be distributed among several individual isolator mounts.*"
|
| 516 |
+
)
|
| 517 |
+
|
| 518 |
+
return question, solution
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def main():
|
| 522 |
+
"""
|
| 523 |
+
Generate numerous instances of each free harmonically excited vibrations
|
| 524 |
+
template with different random seeds and write the results to a JSONL file.
|
| 525 |
+
"""
|
| 526 |
+
import json
|
| 527 |
+
import os
|
| 528 |
+
|
| 529 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 530 |
+
output_file = "testset/mechanical_engineering/vibrations_and_acoustics/harmonically_excited_vibrations.jsonl"
|
| 531 |
+
|
| 532 |
+
# Create the directory if it doesn't exist
|
| 533 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 534 |
+
|
| 535 |
+
# List of template functions with their ID and level
|
| 536 |
+
templates = [
|
| 537 |
+
(template_system_properties, "system_properties", "Easy"),
|
| 538 |
+
(template_rotating_unbalance, "rotating_unbalance", "Intermediate"),
|
| 539 |
+
(template_vibration_transmissibility, "vibration_transmissibility", "Intermediate"),
|
| 540 |
+
(template_vibration_isolator_design, "vibration_isolator_design", "Advanced"),
|
| 541 |
+
]
|
| 542 |
+
|
| 543 |
+
# List to store all generated problems
|
| 544 |
+
all_problems = []
|
| 545 |
+
|
| 546 |
+
# Generate problems for each template
|
| 547 |
+
for template_func, id_name, level in templates:
|
| 548 |
+
for _ in range(50):
|
| 549 |
+
# Generate a unique seed for each problem
|
| 550 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 551 |
+
random.seed(seed)
|
| 552 |
+
|
| 553 |
+
# Generate the problem and solution
|
| 554 |
+
question, solution = template_func()
|
| 555 |
+
|
| 556 |
+
# Create a JSON entry
|
| 557 |
+
problem_entry = {
|
| 558 |
+
"seed": seed,
|
| 559 |
+
"branch": "mechanical_engineering",
|
| 560 |
+
"domain": "vibrations_and_acoustics",
|
| 561 |
+
"area": "harmonically_excited_vibrations",
|
| 562 |
+
"id": id_name,
|
| 563 |
+
"level": level,
|
| 564 |
+
"question": question,
|
| 565 |
+
"solution": solution
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
# Add to the list of problems
|
| 569 |
+
all_problems.append(problem_entry)
|
| 570 |
+
|
| 571 |
+
# Shuffle the problems to mix templates and levels
|
| 572 |
+
random.shuffle(all_problems)
|
| 573 |
+
|
| 574 |
+
# Write all problems to a .jsonl file
|
| 575 |
+
with open(output_file, "w") as file:
|
| 576 |
+
for problem in all_problems:
|
| 577 |
+
file.write(json.dumps(problem))
|
| 578 |
+
file.write("\n")
|
| 579 |
+
|
| 580 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
if __name__ == "__main__":
|
| 584 |
+
main()
|
data/templates/branches/mechanical_engineering/vibrations_and_acoustics/single_degree_of_freedom_systems.py
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import math
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Template 1 (Easy)
|
| 6 |
+
def template_undamped_natural_frequency_translational():
|
| 7 |
+
"""
|
| 8 |
+
Free Vibration: Undamped Natural Frequency of a Translational System
|
| 9 |
+
|
| 10 |
+
Scenario:
|
| 11 |
+
This template generates a fundamental problem on a simple spring-mass
|
| 12 |
+
system. It tests the ability to calculate the key characteristics of
|
| 13 |
+
its undamped free vibration: the natural frequency in both rad/s (omega_n)
|
| 14 |
+
and Hertz (f_n), and the natural period (tau_n).
|
| 15 |
+
|
| 16 |
+
Core Equations:
|
| 17 |
+
omega_n = sqrt(k / m)
|
| 18 |
+
f_n = omega_n / (2 * pi)
|
| 19 |
+
tau_n = 1 / f_n
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
tuple: A tuple containing:
|
| 23 |
+
- str: A question asking for the system's natural frequencies and period.
|
| 24 |
+
- str: A step-by-step solution to the problem.
|
| 25 |
+
"""
|
| 26 |
+
# 1. Parameterize the inputs with random values for high diversity
|
| 27 |
+
|
| 28 |
+
# Mass (m): Chosen from a wide range to represent different scales,
|
| 29 |
+
# from small mechanical parts to larger objects.
|
| 30 |
+
mass = round(random.uniform(0.5, 750.0), 2) # in kg
|
| 31 |
+
|
| 32 |
+
# Stiffness (k): Integer values representing a typical range for mechanical springs.
|
| 33 |
+
stiffness = random.randint(500, 250000) # in N/m
|
| 34 |
+
|
| 35 |
+
# Standardize precision for all calculations and final outputs
|
| 36 |
+
precision = 3
|
| 37 |
+
|
| 38 |
+
# 2. Perform the core calculations for the solution
|
| 39 |
+
|
| 40 |
+
# Step A: Calculate the undamped natural frequency in radians per second
|
| 41 |
+
omega_n = math.sqrt(stiffness / mass)
|
| 42 |
+
|
| 43 |
+
# Step B: Convert the natural frequency from rad/s to Hertz (Hz)
|
| 44 |
+
f_n = omega_n / (2 * math.pi)
|
| 45 |
+
|
| 46 |
+
# Step C: Calculate the natural period of oscillation
|
| 47 |
+
tau_n = 1 / f_n
|
| 48 |
+
|
| 49 |
+
# 3. Generate the question and solution strings
|
| 50 |
+
|
| 51 |
+
question = (
|
| 52 |
+
f"An undamped single-degree-of-freedom system consists of a mass of {mass} kg "
|
| 53 |
+
f"and a spring with a stiffness of {stiffness} N/m. "
|
| 54 |
+
f"Determine the system's undamped natural frequency in both radians per second (rad/s) and Hertz (Hz). "
|
| 55 |
+
f"Also, calculate the natural period of vibration."
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
solution = (
|
| 59 |
+
f"**Given:**\n"
|
| 60 |
+
f"Mass (m) = {mass} kg\n"
|
| 61 |
+
f"Spring Stiffness (k) = {stiffness} N/m\n\n"
|
| 62 |
+
|
| 63 |
+
f"**Step 1:** Calculate the undamped natural frequency in radians per second (omega_n).\n"
|
| 64 |
+
f"The formula is: omega_n = sqrt(k / m)\n"
|
| 65 |
+
f"omega_n = sqrt({stiffness} / {mass})\n"
|
| 66 |
+
f"omega_n = {round(omega_n, precision)} rad/s\n\n"
|
| 67 |
+
|
| 68 |
+
f"**Step 2:** Convert the natural frequency to Hertz (f_n).\n"
|
| 69 |
+
f"The formula is: f_n = omega_n / (2 * pi)\n"
|
| 70 |
+
f"f_n = {round(omega_n, precision)} / (2 * pi)\n"
|
| 71 |
+
f"f_n = {round(f_n, precision)} Hz\n\n"
|
| 72 |
+
|
| 73 |
+
f"**Step 3:** Calculate the natural period of vibration (tau_n).\n"
|
| 74 |
+
f"The period is the reciprocal of the frequency in Hz.\n"
|
| 75 |
+
f"The formula is: tau_n = 1 / f_n\n"
|
| 76 |
+
f"tau_n = 1 / {round(f_n, precision)}\n"
|
| 77 |
+
f"tau_n = {round(tau_n, precision)} s\n\n"
|
| 78 |
+
|
| 79 |
+
f"**Answer:**\n"
|
| 80 |
+
f"The undamped natural frequency is {round(omega_n, precision)} rad/s or {round(f_n, precision)} Hz.\n"
|
| 81 |
+
f"The natural period of vibration is {round(tau_n, precision)} seconds."
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
return question, solution
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# Template 2 (Easy)
|
| 88 |
+
def template_undamped_natural_frequency_torsional():
|
| 89 |
+
"""
|
| 90 |
+
Free Vibration: Undamped Natural Frequency of a Torsional System
|
| 91 |
+
|
| 92 |
+
Scenario:
|
| 93 |
+
This template generates a problem for a simple torsional system, which
|
| 94 |
+
typically consists of a disc or flywheel attached to a shaft. It is the
|
| 95 |
+
rotational equivalent of the spring-mass system. The goal is to calculate
|
| 96 |
+
the natural frequency and period of torsional oscillation.
|
| 97 |
+
|
| 98 |
+
Core Equations:
|
| 99 |
+
omega_n = sqrt(k_t / J_0)
|
| 100 |
+
f_n = omega_n / (2 * pi)
|
| 101 |
+
tau_n = 1 / f_n
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
tuple: A tuple containing:
|
| 105 |
+
- str: A question asking for the system's torsional natural frequencies and period.
|
| 106 |
+
- str: A step-by-step solution to the problem.
|
| 107 |
+
"""
|
| 108 |
+
# 1. Parameterize the inputs with random values
|
| 109 |
+
|
| 110 |
+
# Mass moment of inertia (J_0): Represents the rotational inertia of the disc.
|
| 111 |
+
# The range covers small rotors to medium-sized flywheels.
|
| 112 |
+
mass_moment_of_inertia = round(random.uniform(0.05, 25.0), 3) # in kg-m^2
|
| 113 |
+
|
| 114 |
+
# Torsional stiffness (k_t): Represents the shaft's resistance to twisting.
|
| 115 |
+
torsional_stiffness = random.randint(200, 75000) # in N-m/rad
|
| 116 |
+
|
| 117 |
+
# Standardize precision for all calculations and final outputs
|
| 118 |
+
precision = 3
|
| 119 |
+
|
| 120 |
+
# 2. Perform the core calculations for the solution
|
| 121 |
+
|
| 122 |
+
# Step A: Calculate the undamped natural frequency in radians per second
|
| 123 |
+
omega_n = math.sqrt(torsional_stiffness / mass_moment_of_inertia)
|
| 124 |
+
|
| 125 |
+
# Step B: Convert the natural frequency from rad/s to Hertz (Hz)
|
| 126 |
+
f_n = omega_n / (2 * math.pi)
|
| 127 |
+
|
| 128 |
+
# Step C: Calculate the natural period of oscillation
|
| 129 |
+
tau_n = 1 / f_n
|
| 130 |
+
|
| 131 |
+
# 3. Generate the question and solution strings
|
| 132 |
+
|
| 133 |
+
question = (
|
| 134 |
+
f"A torsional pendulum consists of a disc with a mass moment of inertia of "
|
| 135 |
+
f"{mass_moment_of_inertia} kg-m^2 attached to a shaft with a torsional stiffness of "
|
| 136 |
+
f"{torsional_stiffness} N-m/rad. The system is undamped. "
|
| 137 |
+
f"Calculate the natural frequency of torsional vibration in both rad/s and Hz, "
|
| 138 |
+
f"and determine the corresponding period."
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
solution = (
|
| 142 |
+
f"**Given:**\n"
|
| 143 |
+
f"Mass Moment of Inertia (J_0) = {mass_moment_of_inertia} kg-m^2\n"
|
| 144 |
+
f"Torsional Stiffness (k_t) = {torsional_stiffness} N-m/rad\n\n"
|
| 145 |
+
|
| 146 |
+
f"**Step 1:** Calculate the undamped natural frequency in radians per second (omega_n).\n"
|
| 147 |
+
f"The formula for a torsional system is: omega_n = sqrt(k_t / J_0)\n"
|
| 148 |
+
f"omega_n = sqrt({torsional_stiffness} / {mass_moment_of_inertia})\n"
|
| 149 |
+
f"omega_n = {round(omega_n, precision)} rad/s\n\n"
|
| 150 |
+
|
| 151 |
+
f"**Step 2:** Convert the natural frequency to Hertz (f_n).\n"
|
| 152 |
+
f"The relationship is: f_n = omega_n / (2 * pi)\n"
|
| 153 |
+
f"f_n = {round(omega_n, precision)} / (2 * pi)\n"
|
| 154 |
+
f"f_n = {round(f_n, precision)} Hz\n\n"
|
| 155 |
+
|
| 156 |
+
f"**Step 3:** Calculate the natural period of vibration (tau_n).\n"
|
| 157 |
+
f"The period is the reciprocal of the frequency in Hz.\n"
|
| 158 |
+
f"The formula is: tau_n = 1 / f_n\n"
|
| 159 |
+
f"tau_n = 1 / {round(f_n, precision)}\n"
|
| 160 |
+
f"tau_n = {round(tau_n, precision)} s\n\n"
|
| 161 |
+
|
| 162 |
+
f"**Answer:**\n"
|
| 163 |
+
f"The undamped torsional natural frequency is {round(omega_n, precision)} rad/s or {round(f_n, precision)} Hz.\n"
|
| 164 |
+
f"The natural period of vibration is {round(tau_n, precision)} seconds."
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
return question, solution
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
# Template 3 (Intermediate)
|
| 171 |
+
def template_undamped_response_initial_conditions():
|
| 172 |
+
"""
|
| 173 |
+
Free Vibration: Response of an Undamped System to Initial Conditions
|
| 174 |
+
|
| 175 |
+
Scenario:
|
| 176 |
+
This template assesses the ability to determine the specific equation
|
| 177 |
+
of motion, x(t), for an undamped spring-mass system given a set of
|
| 178 |
+
initial conditions (displacement and velocity). This requires first
|
| 179 |
+
finding the natural frequency and then solving for the two constants
|
| 180 |
+
in the general solution.
|
| 181 |
+
|
| 182 |
+
Core Equations:
|
| 183 |
+
omega_n = sqrt(k / m)
|
| 184 |
+
General Solution: x(t) = A1 * cos(omega_n * t) + A2 * sin(omega_n * t)
|
| 185 |
+
From initial conditions: A1 = x(0) and A2 = v(0) / omega_n
|
| 186 |
+
|
| 187 |
+
Returns:
|
| 188 |
+
tuple: A tuple containing:
|
| 189 |
+
- str: A question asking for the equation of motion.
|
| 190 |
+
- str: A step-by-step solution deriving the equation.
|
| 191 |
+
"""
|
| 192 |
+
# 1. Parameterize the inputs with random values
|
| 193 |
+
mass = round(random.uniform(1.0, 500.0), 2) # in kg
|
| 194 |
+
stiffness = random.randint(1000, 200000) # in N/m
|
| 195 |
+
|
| 196 |
+
# Randomize initial conditions, including cases where one might be zero.
|
| 197 |
+
# Displacement is given in mm for the question, velocity in m/s.
|
| 198 |
+
initial_disp_mm = 0
|
| 199 |
+
initial_vel_ms = 0.0
|
| 200 |
+
|
| 201 |
+
# Use a chooser to create varied scenarios
|
| 202 |
+
# 1: Only displacement, 2: Only velocity, 3: Both are non-zero
|
| 203 |
+
scenario_choice = random.randint(1, 3)
|
| 204 |
+
if scenario_choice == 1:
|
| 205 |
+
initial_disp_mm = random.randint(-100, 100)
|
| 206 |
+
while initial_disp_mm == 0: initial_disp_mm = random.randint(-100, 100)
|
| 207 |
+
elif scenario_choice == 2:
|
| 208 |
+
initial_vel_ms = round(random.uniform(-5.0, 5.0), 2)
|
| 209 |
+
while initial_vel_ms == 0.0: initial_vel_ms = round(random.uniform(-5.0, 5.0), 2)
|
| 210 |
+
else: # scenario_choice == 3
|
| 211 |
+
initial_disp_mm = random.randint(-100, 100)
|
| 212 |
+
initial_vel_ms = round(random.uniform(-5.0, 5.0), 2)
|
| 213 |
+
while initial_disp_mm == 0: initial_disp_mm = random.randint(-100, 100)
|
| 214 |
+
while initial_vel_ms == 0.0: initial_vel_ms = round(random.uniform(-5.0, 5.0), 2)
|
| 215 |
+
|
| 216 |
+
# Convert initial displacement from mm to meters for calculations
|
| 217 |
+
initial_disp_m = initial_disp_mm / 1000.0
|
| 218 |
+
|
| 219 |
+
precision = 4
|
| 220 |
+
|
| 221 |
+
# 2. Perform the core calculations for the solution
|
| 222 |
+
|
| 223 |
+
# Step A: Calculate the natural frequency
|
| 224 |
+
omega_n = math.sqrt(stiffness / mass)
|
| 225 |
+
|
| 226 |
+
# Step B: Determine the constants A1 and A2 from initial conditions
|
| 227 |
+
A1 = initial_disp_m
|
| 228 |
+
A2 = initial_vel_ms / omega_n
|
| 229 |
+
|
| 230 |
+
# 3. Generate the question and solution strings
|
| 231 |
+
|
| 232 |
+
question = (
|
| 233 |
+
f"An undamped spring-mass system has a mass of {mass} kg and a spring stiffness of {stiffness} N/m. "
|
| 234 |
+
f"The mass is given an initial displacement of {initial_disp_mm} mm and an initial velocity of {initial_vel_ms} m/s. "
|
| 235 |
+
f"Determine the equation of motion, x(t), for the system."
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
solution = (
|
| 239 |
+
f"**Given:**\n"
|
| 240 |
+
f"Mass (m) = {mass} kg\n"
|
| 241 |
+
f"Stiffness (k) = {stiffness} N/m\n"
|
| 242 |
+
f"Initial Displacement (x(0)) = {initial_disp_mm} mm = {initial_disp_m} m\n"
|
| 243 |
+
f"Initial Velocity (v(0)) = {initial_vel_ms} m/s\n\n"
|
| 244 |
+
|
| 245 |
+
f"**Step 1:** Calculate the undamped natural frequency (omega_n).\n"
|
| 246 |
+
f"omega_n = sqrt(k / m) = sqrt({stiffness} / {mass}) = {round(omega_n, precision)} rad/s\n\n"
|
| 247 |
+
|
| 248 |
+
f"**Step 2:** State the general form of the solution for an undamped system.\n"
|
| 249 |
+
f"The general solution is: x(t) = A1 * cos(omega_n * t) + A2 * sin(omega_n * t)\n\n"
|
| 250 |
+
|
| 251 |
+
f"**Step 3:** Apply the initial conditions to find the constants A1 and A2.\n"
|
| 252 |
+
f"First, apply the initial displacement at t=0:\n"
|
| 253 |
+
f"x(0) = A1 * cos(0) + A2 * sin(0) = A1\n"
|
| 254 |
+
f"Therefore, A1 = x(0) = {initial_disp_m} m\n\n"
|
| 255 |
+
|
| 256 |
+
f"Next, find the derivative of x(t) to get the velocity, v(t):\n"
|
| 257 |
+
f"v(t) = dx/dt = -A1 * omega_n * sin(omega_n * t) + A2 * omega_n * cos(omega_n * t)\n"
|
| 258 |
+
f"Now apply the initial velocity at t=0:\n"
|
| 259 |
+
f"v(0) = -A1 * omega_n * sin(0) + A2 * omega_n * cos(0) = A2 * omega_n\n"
|
| 260 |
+
f"Therefore, A2 = v(0) / omega_n = {initial_vel_ms} / {round(omega_n, precision)} = {round(A2, precision)} m\n\n"
|
| 261 |
+
|
| 262 |
+
f"**Step 4:** Substitute the constants and omega_n into the general solution.\n"
|
| 263 |
+
f"x(t) = {round(A1, precision)} * cos({round(omega_n, precision)} * t) + ({round(A2, precision)}) * sin({round(omega_n, precision)} * t)\n\n"
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# Clean up the final equation for better readability
|
| 267 |
+
cos_term = ""
|
| 268 |
+
if abs(A1) > 1e-9: # Use a small tolerance to handle floating point inaccuracies
|
| 269 |
+
cos_term = f"{round(A1, precision)}*cos({round(omega_n, precision)}*t)"
|
| 270 |
+
|
| 271 |
+
sin_term = ""
|
| 272 |
+
if abs(A2) > 1e-9:
|
| 273 |
+
sign = "-" if A2 < 0 else "+"
|
| 274 |
+
# If the cos_term is empty, don't start with a plus sign
|
| 275 |
+
if not cos_term and A2 > 0:
|
| 276 |
+
sign = ""
|
| 277 |
+
|
| 278 |
+
sin_term = f" {sign} {abs(round(A2, precision))}*sin({round(omega_n, precision)}*t)"
|
| 279 |
+
# Remove leading space if it's the first term
|
| 280 |
+
if not cos_term:
|
| 281 |
+
sin_term = sin_term.lstrip()
|
| 282 |
+
|
| 283 |
+
final_equation = cos_term + sin_term
|
| 284 |
+
if not final_equation: final_equation = "0"
|
| 285 |
+
|
| 286 |
+
solution += (
|
| 287 |
+
f"**Answer:**\n"
|
| 288 |
+
f"The final equation of motion is:\n"
|
| 289 |
+
f"x(t) = {final_equation} (m)"
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
return question, solution
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
# Template 4 (Intermediate)
|
| 296 |
+
def template_damping_classification():
|
| 297 |
+
"""
|
| 298 |
+
Free Vibration: Damping Parameters and System Classification
|
| 299 |
+
|
| 300 |
+
Scenario:
|
| 301 |
+
This template introduces viscous damping and requires the calculation of
|
| 302 |
+
key damping parameters. Given a system's physical properties (mass,
|
| 303 |
+
stiffness, damping coefficient), the goal is to determine the damping
|
| 304 |
+
ratio (zeta), classify the system's behavior (underdamped, critically
|
| 305 |
+
damped, or overdamped), and calculate the damped natural frequency
|
| 306 |
+
(omega_d) if the system is underdamped.
|
| 307 |
+
|
| 308 |
+
Core Equations:
|
| 309 |
+
omega_n = sqrt(k / m)
|
| 310 |
+
c_c = 2 * sqrt(k * m)
|
| 311 |
+
zeta = c / c_c
|
| 312 |
+
omega_d = omega_n * sqrt(1 - zeta^2) (for zeta < 1)
|
| 313 |
+
|
| 314 |
+
Returns:
|
| 315 |
+
tuple: A tuple containing:
|
| 316 |
+
- str: A question asking for system classification and key parameters.
|
| 317 |
+
- str: A step-by-step solution.
|
| 318 |
+
"""
|
| 319 |
+
# 1. Parameterize the inputs to ensure all cases are generated
|
| 320 |
+
mass = round(random.uniform(2.0, 600.0), 2) # in kg
|
| 321 |
+
stiffness = random.randint(2000, 300000) # in N/m
|
| 322 |
+
|
| 323 |
+
# To ensure a good distribution of outcomes, we first select a damping ratio
|
| 324 |
+
# and then calculate the corresponding damping coefficient 'c'.
|
| 325 |
+
|
| 326 |
+
# Choose a scenario: 1 for underdamped, 2 for critically damped, 3 for overdamped
|
| 327 |
+
scenario_choice = random.randint(1, 3)
|
| 328 |
+
|
| 329 |
+
if scenario_choice == 1: # Underdamped
|
| 330 |
+
zeta = round(random.uniform(0.15, 0.85), 3)
|
| 331 |
+
elif scenario_choice == 2: # Critically Damped
|
| 332 |
+
zeta = 1.0
|
| 333 |
+
else: # Overdamped
|
| 334 |
+
zeta = round(random.uniform(1.2, 2.5), 3)
|
| 335 |
+
|
| 336 |
+
# Standardize precision for final outputs
|
| 337 |
+
precision = 4
|
| 338 |
+
|
| 339 |
+
# 2. Perform the core calculations for the solution
|
| 340 |
+
|
| 341 |
+
# Step A: Calculate the critical damping coefficient (c_c)
|
| 342 |
+
critical_damping_c = 2 * math.sqrt(stiffness * mass)
|
| 343 |
+
|
| 344 |
+
# Step B: Calculate the actual damping coefficient (c) based on the desired zeta
|
| 345 |
+
damping_coefficient_c = zeta * critical_damping_c
|
| 346 |
+
|
| 347 |
+
# Step C: Determine the system classification based on zeta
|
| 348 |
+
if zeta < 1:
|
| 349 |
+
classification = "Underdamped"
|
| 350 |
+
# Also calculate natural and damped frequencies for the solution
|
| 351 |
+
omega_n = math.sqrt(stiffness / mass)
|
| 352 |
+
omega_d = omega_n * math.sqrt(1 - zeta**2)
|
| 353 |
+
elif zeta == 1:
|
| 354 |
+
classification = "Critically Damped"
|
| 355 |
+
else:
|
| 356 |
+
classification = "Overdamped"
|
| 357 |
+
|
| 358 |
+
# 3. Generate the question and solution strings
|
| 359 |
+
|
| 360 |
+
question = (
|
| 361 |
+
f"A damped single-degree-of-freedom system has a mass of {mass} kg, "
|
| 362 |
+
f"a spring stiffness of {stiffness} N/m, and a viscous damping coefficient of "
|
| 363 |
+
f"{round(damping_coefficient_c, 2)} N-s/m. "
|
| 364 |
+
f"Calculate the damping ratio (zeta) and classify the system as underdamped, "
|
| 365 |
+
f"critically damped, or overdamped. If the system is underdamped, also "
|
| 366 |
+
f"calculate its damped natural frequency (omega_d)."
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
solution = (
|
| 370 |
+
f"**Given:**\n"
|
| 371 |
+
f"Mass (m) = {mass} kg\n"
|
| 372 |
+
f"Stiffness (k) = {stiffness} N/m\n"
|
| 373 |
+
f"Damping Coefficient (c) = {round(damping_coefficient_c, 2)} N-s/m\n\n"
|
| 374 |
+
|
| 375 |
+
f"**Step 1:** Calculate the critical damping coefficient (c_c).\n"
|
| 376 |
+
f"The formula is: c_c = 2 * sqrt(k * m)\n"
|
| 377 |
+
f"c_c = 2 * sqrt({stiffness} * {mass})\n"
|
| 378 |
+
f"c_c = {round(critical_damping_c, precision)} N-s/m\n\n"
|
| 379 |
+
|
| 380 |
+
f"**Step 2:** Calculate the damping ratio (zeta).\n"
|
| 381 |
+
f"The formula is: zeta = c / c_c\n"
|
| 382 |
+
f"zeta = {round(damping_coefficient_c, 2)} / {round(critical_damping_c, precision)}\n"
|
| 383 |
+
f"zeta = {round(zeta, precision)}\n\n"
|
| 384 |
+
|
| 385 |
+
f"**Step 3:** Classify the system based on the value of zeta.\n"
|
| 386 |
+
f"Since zeta {'>' if zeta > 1 else '<' if zeta < 1 else '='} 1, the system is **{classification}**.\n\n"
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
# Add the final step only if the system is underdamped
|
| 390 |
+
if classification == "Underdamped":
|
| 391 |
+
solution += (
|
| 392 |
+
f"**Step 4:** Since the system is underdamped, calculate the damped natural frequency (omega_d).\n"
|
| 393 |
+
f"First, find the undamped natural frequency (omega_n):\n"
|
| 394 |
+
f"omega_n = sqrt(k / m) = sqrt({stiffness} / {mass}) = {round(omega_n, precision)} rad/s\n\n"
|
| 395 |
+
f"Now, use the formula: omega_d = omega_n * sqrt(1 - zeta^2)\n"
|
| 396 |
+
f"omega_d = {round(omega_n, precision)} * sqrt(1 - {round(zeta, precision)}^2)\n"
|
| 397 |
+
f"omega_d = {round(omega_d, precision)} rad/s\n\n"
|
| 398 |
+
|
| 399 |
+
f"**Answer:**\n"
|
| 400 |
+
f"The damping ratio is {round(zeta, precision)}. The system is **{classification}**.\n"
|
| 401 |
+
f"The damped natural frequency is {round(omega_d, precision)} rad/s."
|
| 402 |
+
)
|
| 403 |
+
else:
|
| 404 |
+
solution += (
|
| 405 |
+
f"**Answer:**\n"
|
| 406 |
+
f"The damping ratio is {round(zeta, precision)}. The system is **{classification}**."
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
return question, solution
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
# Template 5 (Intermediate)
|
| 413 |
+
def template_logarithmic_decrement():
|
| 414 |
+
"""
|
| 415 |
+
Free Vibration: Logarithmic Decrement and Damping Ratio
|
| 416 |
+
|
| 417 |
+
Scenario:
|
| 418 |
+
This template models a practical method for determining the damping in
|
| 419 |
+
an underdamped system. By observing the amplitude of vibration at two
|
| 420 |
+
distinct points in time, separated by a known number of cycles, one can
|
| 421 |
+
calculate the logarithmic decrement, which directly relates to the system's
|
| 422 |
+
damping ratio.
|
| 423 |
+
|
| 424 |
+
Core Equations:
|
| 425 |
+
delta = (1/n) * ln(x1 / x_{n+1})
|
| 426 |
+
zeta = delta / sqrt((2*pi)^2 + delta^2)
|
| 427 |
+
|
| 428 |
+
Returns:
|
| 429 |
+
tuple: A tuple containing:
|
| 430 |
+
- str: A question asking for the logarithmic decrement and damping ratio.
|
| 431 |
+
- str: A step-by-step solution.
|
| 432 |
+
"""
|
| 433 |
+
# 1. Parameterize the inputs with random values
|
| 434 |
+
|
| 435 |
+
# Initialize variables to enter the loop
|
| 436 |
+
final_amplitude_x_n_plus_1 = 0.0
|
| 437 |
+
|
| 438 |
+
# Use a loop to ensure the final rounded amplitude is never zero.
|
| 439 |
+
# This prevents a division-by-zero error in cases of high decay.
|
| 440 |
+
while final_amplitude_x_n_plus_1 <= 0.0:
|
| 441 |
+
# To ensure physically consistent values, we first generate a realistic
|
| 442 |
+
# damping ratio (zeta) and work backward to find the amplitudes.
|
| 443 |
+
zeta_actual = random.uniform(0.05, 0.25)
|
| 444 |
+
|
| 445 |
+
# Calculate the corresponding logarithmic decrement
|
| 446 |
+
delta_actual = (2 * math.pi * zeta_actual) / math.sqrt(1 - zeta_actual**2)
|
| 447 |
+
|
| 448 |
+
# Choose a number of cycles for the measurement
|
| 449 |
+
num_cycles_n = random.randint(2, 8) # Reduced max cycles to lower chance of zeroing out
|
| 450 |
+
|
| 451 |
+
# Set a random initial amplitude in mm
|
| 452 |
+
initial_amplitude_x1 = round(random.uniform(20.0, 100.0), 1)
|
| 453 |
+
|
| 454 |
+
# Calculate the final amplitude based on the decrement and number of cycles
|
| 455 |
+
calculated_final_amplitude = initial_amplitude_x1 / math.exp(num_cycles_n * delta_actual)
|
| 456 |
+
|
| 457 |
+
# Round the final amplitude to make it look like a measured value
|
| 458 |
+
final_amplitude_x_n_plus_1 = round(calculated_final_amplitude, 1)
|
| 459 |
+
|
| 460 |
+
# Standardize precision for final outputs in the solution
|
| 461 |
+
precision = 4
|
| 462 |
+
|
| 463 |
+
# 2. Perform the core calculations for the solution (as the user would)
|
| 464 |
+
|
| 465 |
+
# Step A: Calculate the logarithmic decrement from the given amplitudes
|
| 466 |
+
log_decrement_delta = (1 / num_cycles_n) * math.log(initial_amplitude_x1 / final_amplitude_x_n_plus_1)
|
| 467 |
+
|
| 468 |
+
# Step B: Calculate the damping ratio from the decrement
|
| 469 |
+
damping_ratio_zeta = log_decrement_delta / math.sqrt((2 * math.pi)**2 + log_decrement_delta**2)
|
| 470 |
+
|
| 471 |
+
# 3. Generate the question and solution strings
|
| 472 |
+
|
| 473 |
+
question = (
|
| 474 |
+
f"The amplitude of free vibration of an underdamped system is observed to decay over time. "
|
| 475 |
+
f"The initial amplitude is measured to be {initial_amplitude_x1} mm. After {num_cycles_n} complete cycles, "
|
| 476 |
+
f"the amplitude is measured to be {final_amplitude_x_n_plus_1} mm. "
|
| 477 |
+
f"Based on these observations, calculate the logarithmic decrement (delta) and the damping ratio (zeta) of the system."
|
| 478 |
+
)
|
| 479 |
+
|
| 480 |
+
solution = (
|
| 481 |
+
f"**Given:**\n"
|
| 482 |
+
f"Initial Amplitude (x1) = {initial_amplitude_x1} mm\n"
|
| 483 |
+
f"Amplitude after {num_cycles_n} cycles (x{num_cycles_n + 1}) = {final_amplitude_x_n_plus_1} mm\n"
|
| 484 |
+
f"Number of cycles (n) = {num_cycles_n}\n\n"
|
| 485 |
+
|
| 486 |
+
f"**Step 1:** Calculate the logarithmic decrement (delta).\n"
|
| 487 |
+
f"The formula is: delta = (1/n) * ln(x1 / x_{num_cycles_n + 1})\n"
|
| 488 |
+
f"delta = (1 / {num_cycles_n}) * ln({initial_amplitude_x1} / {final_amplitude_x_n_plus_1})\n"
|
| 489 |
+
f"delta = (1 / {num_cycles_n}) * ln({round(initial_amplitude_x1 / final_amplitude_x_n_plus_1, precision)})\n"
|
| 490 |
+
f"delta = {round(log_decrement_delta, precision)}\n\n"
|
| 491 |
+
|
| 492 |
+
f"**Step 2:** Calculate the damping ratio (zeta) from the logarithmic decrement.\n"
|
| 493 |
+
f"The formula relating zeta and delta is: zeta = delta / sqrt((2*pi)^2 + delta^2)\n"
|
| 494 |
+
f"zeta = {round(log_decrement_delta, precision)} / sqrt((2*pi)^2 + {round(log_decrement_delta, precision)}^2)\n"
|
| 495 |
+
f"zeta = {round(damping_ratio_zeta, precision)}\n\n"
|
| 496 |
+
|
| 497 |
+
f"**Answer:**\n"
|
| 498 |
+
f"The logarithmic decrement is {round(log_decrement_delta, precision)}.\n"
|
| 499 |
+
f"The damping ratio of the system is {round(damping_ratio_zeta, precision)}."
|
| 500 |
+
)
|
| 501 |
+
|
| 502 |
+
return question, solution
|
| 503 |
+
|
| 504 |
+
|
| 505 |
+
# Template 6 (Advanced)
|
| 506 |
+
def template_equivalent_stiffness_frequency():
|
| 507 |
+
"""
|
| 508 |
+
Free Vibration: Equivalent Stiffness and Natural Frequency
|
| 509 |
+
|
| 510 |
+
Scenario:
|
| 511 |
+
This template addresses systems with multiple springs. It requires first
|
| 512 |
+
simplifying a spring network (either in series or parallel) into a single
|
| 513 |
+
equivalent spring. After finding the equivalent stiffness (k_eq), the
|
| 514 |
+
natural frequency of the overall system is calculated. This adds a
|
| 515 |
+
critical modeling step to the standard frequency calculation.
|
| 516 |
+
|
| 517 |
+
Core Equations:
|
| 518 |
+
Parallel Stiffness: k_eq = k1 + k2
|
| 519 |
+
Series Stiffness: k_eq = (k1 * k2) / (k1 + k2)
|
| 520 |
+
Natural Frequency: omega_n = sqrt(k_eq / m)
|
| 521 |
+
|
| 522 |
+
Returns:
|
| 523 |
+
tuple: A tuple containing:
|
| 524 |
+
- str: A question asking for the equivalent stiffness and natural frequency.
|
| 525 |
+
- str: A step-by-step solution.
|
| 526 |
+
"""
|
| 527 |
+
# 1. Parameterize the inputs with random values
|
| 528 |
+
mass = round(random.uniform(5.0, 100.0), 2) # in kg
|
| 529 |
+
stiffness_1 = random.randint(1000, 50000) # in N/m
|
| 530 |
+
stiffness_2 = random.randint(1000, 50000) # in N/m
|
| 531 |
+
|
| 532 |
+
# Randomly choose the spring configuration
|
| 533 |
+
configuration = random.choice(['series', 'parallel'])
|
| 534 |
+
|
| 535 |
+
# Standardize precision for final outputs
|
| 536 |
+
precision = 3
|
| 537 |
+
|
| 538 |
+
# 2. Perform the core calculations for the solution
|
| 539 |
+
|
| 540 |
+
# Step A: Calculate the equivalent stiffness (k_eq) based on the configuration
|
| 541 |
+
if configuration == 'parallel':
|
| 542 |
+
k_eq = stiffness_1 + stiffness_2
|
| 543 |
+
formula_str = "k_eq = k1 + k2"
|
| 544 |
+
calculation_str = f"k_eq = {stiffness_1} + {stiffness_2} = {k_eq} N/m"
|
| 545 |
+
else: # configuration == 'series'
|
| 546 |
+
k_eq = (stiffness_1 * stiffness_2) / (stiffness_1 + stiffness_2)
|
| 547 |
+
formula_str = "k_eq = (k1 * k2) / (k1 + k2)"
|
| 548 |
+
calculation_str = (
|
| 549 |
+
f"k_eq = ({stiffness_1} * {stiffness_2}) / ({stiffness_1} + {stiffness_2}) = "
|
| 550 |
+
f"{round(k_eq, 2)} N/m"
|
| 551 |
+
)
|
| 552 |
+
|
| 553 |
+
# Step B: Calculate the natural frequency using the equivalent stiffness
|
| 554 |
+
omega_n = math.sqrt(k_eq / mass)
|
| 555 |
+
|
| 556 |
+
# 3. Generate the question and solution strings
|
| 557 |
+
|
| 558 |
+
question = (
|
| 559 |
+
f"A mass of {mass} kg is attached to a system of two springs. The first spring has a "
|
| 560 |
+
f"stiffness (k1) of {stiffness_1} N/m, and the second spring has a stiffness (k2) of {stiffness_2} N/m. "
|
| 561 |
+
f"The two springs are arranged in {configuration}. "
|
| 562 |
+
f"Determine the equivalent stiffness of the spring system and the resulting undamped "
|
| 563 |
+
f"natural frequency (omega_n) in rad/s."
|
| 564 |
+
)
|
| 565 |
+
|
| 566 |
+
solution = (
|
| 567 |
+
f"**Given:**\n"
|
| 568 |
+
f"Mass (m) = {mass} kg\n"
|
| 569 |
+
f"Stiffness 1 (k1) = {stiffness_1} N/m\n"
|
| 570 |
+
f"Stiffness 2 (k2) = {stiffness_2} N/m\n"
|
| 571 |
+
f"Configuration = {configuration.capitalize()}\n\n"
|
| 572 |
+
|
| 573 |
+
f"**Step 1:** Calculate the equivalent stiffness (k_eq) for the {configuration} configuration.\n"
|
| 574 |
+
f"The formula for springs in {configuration} is: {formula_str}\n"
|
| 575 |
+
f"{calculation_str}\n\n"
|
| 576 |
+
|
| 577 |
+
f"**Step 2:** Calculate the undamped natural frequency (omega_n) using the equivalent stiffness.\n"
|
| 578 |
+
f"The formula is: omega_n = sqrt(k_eq / m)\n"
|
| 579 |
+
f"omega_n = sqrt({round(k_eq, 2)} / {mass})\n"
|
| 580 |
+
f"omega_n = {round(omega_n, precision)} rad/s\n\n"
|
| 581 |
+
|
| 582 |
+
f"**Answer:**\n"
|
| 583 |
+
f"The equivalent stiffness of the system is {round(k_eq, 2)} N/m.\n"
|
| 584 |
+
f"The undamped natural frequency is {round(omega_n, precision)} rad/s."
|
| 585 |
+
)
|
| 586 |
+
|
| 587 |
+
return question, solution
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
def main():
|
| 591 |
+
"""
|
| 592 |
+
Generate numerous instances of each free vibration of single-degree-of-freedom systems
|
| 593 |
+
template with different random seeds and write the results to a JSONL file.
|
| 594 |
+
"""
|
| 595 |
+
import json
|
| 596 |
+
import os
|
| 597 |
+
|
| 598 |
+
# Define the output path (Modify this path according to where you are running the code from)
|
| 599 |
+
output_file = "testset/mechanical_engineering/vibrations_and_acoustics/single_degree_of_freedom_systems.jsonl"
|
| 600 |
+
|
| 601 |
+
# Create the directory if it doesn't exist
|
| 602 |
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
| 603 |
+
|
| 604 |
+
# List of template functions with their ID and level
|
| 605 |
+
templates = [
|
| 606 |
+
(template_undamped_natural_frequency_translational, "undamped_natural_frequency_translational", "Easy"),
|
| 607 |
+
(template_undamped_natural_frequency_torsional, "undamped_natural_frequency_torsional", "Easy"),
|
| 608 |
+
(template_undamped_response_initial_conditions, "undamped_response_initial_conditions", "Intermediate"),
|
| 609 |
+
(template_damping_classification, "damping_classification", "Intermediate"),
|
| 610 |
+
(template_logarithmic_decrement, "logarithmic_decrement", "Intermediate"),
|
| 611 |
+
(template_equivalent_stiffness_frequency, "equivalent_stiffness_frequency", "Advanced"),
|
| 612 |
+
]
|
| 613 |
+
|
| 614 |
+
# List to store all generated problems
|
| 615 |
+
all_problems = []
|
| 616 |
+
|
| 617 |
+
# Generate problems for each template
|
| 618 |
+
for template_func, id_name, level in templates:
|
| 619 |
+
for _ in range(50):
|
| 620 |
+
# Generate a unique seed for each problem
|
| 621 |
+
seed = random.randint(1_000_000_000, 4_000_000_000)
|
| 622 |
+
random.seed(seed)
|
| 623 |
+
|
| 624 |
+
# Generate the problem and solution
|
| 625 |
+
question, solution = template_func()
|
| 626 |
+
|
| 627 |
+
# Create a JSON entry
|
| 628 |
+
problem_entry = {
|
| 629 |
+
"seed": seed,
|
| 630 |
+
"branch": "mechanical_engineering",
|
| 631 |
+
"domain": "vibrations_and_acoustics",
|
| 632 |
+
"area": "single_degree_of_freedom_systems",
|
| 633 |
+
"id": id_name,
|
| 634 |
+
"level": level,
|
| 635 |
+
"question": question,
|
| 636 |
+
"solution": solution
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
# Add to the list of problems
|
| 640 |
+
all_problems.append(problem_entry)
|
| 641 |
+
|
| 642 |
+
# Shuffle the problems to mix templates and levels
|
| 643 |
+
random.shuffle(all_problems)
|
| 644 |
+
|
| 645 |
+
# Write all problems to a .jsonl file
|
| 646 |
+
with open(output_file, "w") as file:
|
| 647 |
+
for problem in all_problems:
|
| 648 |
+
file.write(json.dumps(problem))
|
| 649 |
+
file.write("\n")
|
| 650 |
+
|
| 651 |
+
print(f"Successfully generated {len(all_problems)} problems and saved to {output_file}")
|
| 652 |
+
|
| 653 |
+
|
| 654 |
+
if __name__ == "__main__":
|
| 655 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.30.0
|
| 2 |
+
pandas
|
| 3 |
+
numpy
|
| 4 |
+
scipy
|
src/__pycache__/storage.cpython-311.pyc
ADDED
|
Binary file (2.25 kB). View file
|
|
|
src/__pycache__/template_loader.cpython-311.pyc
ADDED
|
Binary file (5.16 kB). View file
|
|
|
src/storage.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# src/storage.py
|
| 2 |
+
import json
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
def _get_review_dir() -> Path:
|
| 7 |
+
"""
|
| 8 |
+
If Hugging Face Persistent Storage is enabled, it is mounted at /data.
|
| 9 |
+
Otherwise fall back to repo-local ./reviews (ephemeral on Spaces).
|
| 10 |
+
"""
|
| 11 |
+
persistent_root = Path("/data")
|
| 12 |
+
if persistent_root.exists() and persistent_root.is_dir():
|
| 13 |
+
return persistent_root / "reviews"
|
| 14 |
+
return Path(__file__).parent.parent / "reviews"
|
| 15 |
+
|
| 16 |
+
REVIEW_DIR = _get_review_dir()
|
| 17 |
+
REVIEW_DIR.mkdir(parents=True, exist_ok=True)
|
| 18 |
+
|
| 19 |
+
def save_review(annotator_name, branch, area, template_name, scores, decision, feedback):
|
| 20 |
+
"""
|
| 21 |
+
Appends a single review to an annotator-specific JSONL file.
|
| 22 |
+
"""
|
| 23 |
+
safe_name = "".join([c for c in annotator_name if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_")
|
| 24 |
+
safe_branch = branch.replace(" ", "_")
|
| 25 |
+
|
| 26 |
+
filename = REVIEW_DIR / f"{safe_name}_{safe_branch}.jsonl"
|
| 27 |
+
|
| 28 |
+
review_data = {
|
| 29 |
+
"timestamp": datetime.now().isoformat(),
|
| 30 |
+
"annotator_id": annotator_name,
|
| 31 |
+
"branch": branch,
|
| 32 |
+
"area": area,
|
| 33 |
+
"template": template_name,
|
| 34 |
+
"scores": {
|
| 35 |
+
"physical_plausibility": scores[0],
|
| 36 |
+
"mathematical_correctness": scores[1],
|
| 37 |
+
"pedagogical_clarity": scores[2],
|
| 38 |
+
},
|
| 39 |
+
"decision": decision,
|
| 40 |
+
"feedback": feedback,
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
with open(filename, "a", encoding="utf-8") as f:
|
| 44 |
+
f.write(json.dumps(review_data) + "\n")
|
| 45 |
+
|
| 46 |
+
return str(filename)
|
src/template_loader.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import inspect
|
| 3 |
+
import importlib.util
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
# Define root path relative to this script
|
| 7 |
+
BASE_DIR = Path(__file__).parent.parent / "data" / "templates" / "branches"
|
| 8 |
+
|
| 9 |
+
def get_branches():
|
| 10 |
+
"""Returns a list of available engineering branches."""
|
| 11 |
+
if not BASE_DIR.exists():
|
| 12 |
+
return []
|
| 13 |
+
return sorted([d.name for d in BASE_DIR.iterdir() if d.is_dir() and not d.name.startswith("__")])
|
| 14 |
+
|
| 15 |
+
def get_areas(branch_name):
|
| 16 |
+
"""Returns the sub-areas (directories) for a given branch."""
|
| 17 |
+
branch_path = BASE_DIR / branch_name
|
| 18 |
+
if not branch_path.exists():
|
| 19 |
+
return []
|
| 20 |
+
return sorted([d.name for d in branch_path.iterdir() if d.is_dir() and not d.name.startswith("__")])
|
| 21 |
+
|
| 22 |
+
def get_template_files(branch_name, area_name):
|
| 23 |
+
"""Returns the python filenames (modules) in an area."""
|
| 24 |
+
area_path = BASE_DIR / branch_name / area_name
|
| 25 |
+
if not area_path.exists():
|
| 26 |
+
return []
|
| 27 |
+
|
| 28 |
+
files = [
|
| 29 |
+
f.stem for f in area_path.glob("*.py")
|
| 30 |
+
if f.name not in ["constants.py", "__init__.py"] and not f.name.startswith("__")
|
| 31 |
+
]
|
| 32 |
+
return sorted(files)
|
| 33 |
+
|
| 34 |
+
def load_template_functions(branch, area, filename):
|
| 35 |
+
"""
|
| 36 |
+
Loads a specific file and returns a list of its template functions.
|
| 37 |
+
Returns: List of tuples (function_name, function_object)
|
| 38 |
+
"""
|
| 39 |
+
file_path = BASE_DIR / branch / area / f"{filename}.py"
|
| 40 |
+
if not file_path.exists():
|
| 41 |
+
raise FileNotFoundError(f"File not found: {file_path}")
|
| 42 |
+
|
| 43 |
+
# Dynamic import
|
| 44 |
+
spec = importlib.util.spec_from_file_location(filename, file_path)
|
| 45 |
+
module = importlib.util.module_from_spec(spec)
|
| 46 |
+
|
| 47 |
+
# Add branch folder to path so imports like 'from constants import...' work
|
| 48 |
+
sys.path.append(str(file_path.parent.parent))
|
| 49 |
+
|
| 50 |
+
try:
|
| 51 |
+
spec.loader.exec_module(module)
|
| 52 |
+
except Exception as e:
|
| 53 |
+
return [] # Return empty if module crashes on load
|
| 54 |
+
|
| 55 |
+
# Extract all functions that start with 'template_'
|
| 56 |
+
templates = []
|
| 57 |
+
for name, obj in inspect.getmembers(module):
|
| 58 |
+
if inspect.isfunction(obj) and name.startswith("template_"):
|
| 59 |
+
templates.append((name, obj))
|
| 60 |
+
|
| 61 |
+
return sorted(templates, key=lambda x: x[0])
|
| 62 |
+
|
| 63 |
+
def get_source_code(branch, area, filename):
|
| 64 |
+
"""Returns the raw source code of the entire file."""
|
| 65 |
+
file_path = BASE_DIR / branch / area / f"{filename}.py"
|
| 66 |
+
if not file_path.exists():
|
| 67 |
+
return "# Error: File not found."
|
| 68 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 69 |
+
return f.read()
|
test_loader.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import traceback
|
| 3 |
+
from src.template_loader import (
|
| 4 |
+
get_branches,
|
| 5 |
+
get_areas,
|
| 6 |
+
get_template_files,
|
| 7 |
+
load_template_functions
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
def main():
|
| 11 |
+
print("--- Starting Template Loader Test (V2) ---")
|
| 12 |
+
|
| 13 |
+
# 1. Test Branches
|
| 14 |
+
branches = get_branches()
|
| 15 |
+
print(f"\nBranches Found: {branches}")
|
| 16 |
+
|
| 17 |
+
if not branches:
|
| 18 |
+
print("No branches found. Check your directory structure.")
|
| 19 |
+
return
|
| 20 |
+
|
| 21 |
+
# 2. Test Areas (using the first branch found, likely 'chemical_engineering')
|
| 22 |
+
test_branch = "chemical_engineering"
|
| 23 |
+
if test_branch not in branches:
|
| 24 |
+
test_branch = branches[0]
|
| 25 |
+
|
| 26 |
+
areas = get_areas(test_branch)
|
| 27 |
+
print(f"Areas in '{test_branch}': {areas}")
|
| 28 |
+
|
| 29 |
+
if not areas:
|
| 30 |
+
print(f"No areas found in {test_branch}.")
|
| 31 |
+
return
|
| 32 |
+
|
| 33 |
+
# 3. Test Files (using 'reaction_kinetics' if available)
|
| 34 |
+
test_area = "reaction_kinetics"
|
| 35 |
+
if test_area not in areas:
|
| 36 |
+
test_area = areas[0]
|
| 37 |
+
|
| 38 |
+
files = get_template_files(test_branch, test_area)
|
| 39 |
+
print(f"Files in '{test_branch}/{test_area}': {files}")
|
| 40 |
+
|
| 41 |
+
# We specifically want to test 'mole_balances' if it exists
|
| 42 |
+
target_file = "mole_balances"
|
| 43 |
+
if target_file not in files:
|
| 44 |
+
print(f"WARNING: 'mole_balances.py' not found. Using first available file: {files[0]}")
|
| 45 |
+
target_file = files[0]
|
| 46 |
+
|
| 47 |
+
# 4. Test Function Extraction (The Logic Upgrade)
|
| 48 |
+
print(f"\nInspecting file: '{target_file}.py'...")
|
| 49 |
+
try:
|
| 50 |
+
# This loads the file and finds all def template_...():
|
| 51 |
+
templates = load_template_functions(test_branch, test_area, target_file)
|
| 52 |
+
|
| 53 |
+
if not templates:
|
| 54 |
+
print("No functions starting with 'template_' found in this file.")
|
| 55 |
+
return
|
| 56 |
+
|
| 57 |
+
print(f"Found {len(templates)} template functions:")
|
| 58 |
+
for name, func in templates:
|
| 59 |
+
print(f" - {name}")
|
| 60 |
+
|
| 61 |
+
# 5. Test Execution (Run the first function found)
|
| 62 |
+
first_func_name, first_func_obj = templates[0]
|
| 63 |
+
print(f"\nExecuting '{first_func_name}'...")
|
| 64 |
+
|
| 65 |
+
question, solution = first_func_obj()
|
| 66 |
+
|
| 67 |
+
print("\n--- [Output Preview] ---")
|
| 68 |
+
print(f"Question: {question[:100]}...") # Print first 100 chars
|
| 69 |
+
print(f"Solution: {solution[:100]}...")
|
| 70 |
+
print("------------------------")
|
| 71 |
+
print("SUCCESS: Template loaded and executed correctly.")
|
| 72 |
+
|
| 73 |
+
except Exception as e:
|
| 74 |
+
print(f"CRITICAL ERROR during loading/execution:")
|
| 75 |
+
traceback.print_exc()
|
| 76 |
+
|
| 77 |
+
if __name__ == "__main__":
|
| 78 |
+
main()
|