Nasim-Sami/Dynamic_Student_Simulator
0
1"""2Self-contained dynamic mechatronics MCQ student simulator.3 4This file intentionally does not import from student_simulator.py. It includes5its own question metadata, difficulty helpers, baseline student utilities, and6dynamic normal/boost/slump behavior so the dynamic env and trainer can be copied7as a standalone set.8"""9from __future__ import annotations10 11import math12from copy import deepcopy13from typing import Any14 15import numpy as np16 17 18OPTIONS = ["A", "B", "C", "D"]19MIN_ABILITY = 1020MAX_ABILITY = 3021DIFFICULTY_LOW = 0.022DIFFICULTY_HIGH = 16.023 24 25# ---------------------------------------------------------------------26# 1. Shuffled mechatronics question bank27# ---------------------------------------------------------------------28 29# The original pasted questions were ordered by inherent difficulty. The list30# below is deliberately shuffled so question index/order does not reveal the31# difficulty level during simulation or training. The question_id and32# ASSUMED_INHERENT_DIFFICULTY mapping still preserve the intended difficulty.33QUESTIONS_SHUFFLE_SEED = 2026060234 35QUESTIONS = [{'question_id': 'Q19',36 'question': 'A strain gauge has resistance 120 Ω and gauge factor 2.0. If strain is 1000 microstrain, the '37 'resistance change is approximately:',38 'option_A': '0.024 Ω',39 'option_B': '0.24 Ω',40 'option_C': '2.4 Ω',41 'option_D': '24 Ω',42 'answer': 'B',43 'distractor_strength': {'A': 2.1, 'C': 2.5, 'D': 1.1},44 'topic': 'Strain gauge',45 'subtopic': 'gauge factor calculation',46 'explanation': 'ΔR = GF × strain × R = 2 × 0.001 × 120 = 0.24 Ω.'},47 {'question_id': 'Q47',48 'question': 'A mechatronic positioning system reaches the target but vibrates around it. The sensor is '49 'accurate, and the actuator is strong. What is the most likely control-level issue?',50 'option_A': 'No physical target exists',51 'option_B': 'The system has too little supply voltage only',52 'option_C': 'The display is too bright',53 'option_D': 'Poor damping or excessive loop gain',54 'answer': 'D',55 'distractor_strength': {'A': 0.8, 'B': 1.6, 'C': 0.7},56 'topic': 'Closed-loop dynamic response',57 'subtopic': 'oscillation around setpoint',58 'explanation': 'Oscillation around a target often indicates insufficient damping or overly aggressive '59 'control.'},60 {'question_id': 'Q14',61 'question': 'An inverting op-amp has input resistor 10 kΩ and feedback resistor 50 kΩ. If input voltage is '62 '0.2 V, what is the ideal output voltage?',63 'option_A': '-1.0 V',64 'option_B': '+1.0 V',65 'option_C': '+0.04 V',66 'option_D': '-0.04 V',67 'answer': 'A',68 'distractor_strength': {'B': 2.7, 'C': 1.2, 'D': 1.8},69 'topic': 'Operational amplifier',70 'subtopic': 'inverting amplifier',71 'explanation': 'Gain = -Rf/Rin = -50/10 = -5. Output = -5 × 0.2 = -1.0 V.'},72 {'question_id': 'Q4',73 'question': 'A rotary potentiometer is commonly used to measure:',74 'option_A': 'Shaft angle',75 'option_B': 'Fluid level',76 'option_C': 'Air pressure',77 'option_D': 'Light intensity',78 'answer': 'A',79 'distractor_strength': {'B': 1.7, 'C': 1.3, 'D': 1.1},80 'topic': 'Position sensors',81 'subtopic': 'rotary potentiometer',82 'explanation': 'A rotary potentiometer changes resistance as the shaft rotates, so it can measure angular '83 'position.'},84 {'question_id': 'Q23',85 'question': 'Adding integral action to a controller mainly helps to:',86 'option_A': 'Increase sensor dead band',87 'option_B': 'Block actuator feedback',88 'option_C': 'Reduce controller memory',89 'option_D': 'Remove steady-state offset',90 'answer': 'D',91 'distractor_strength': {'A': 1.8, 'B': 1.2, 'C': 1.5},92 'topic': 'PI control',93 'subtopic': 'integral action',94 'explanation': 'Integral action accumulates error and can eliminate steady-state error.'},95 {'question_id': 'Q18',96 'question': 'A first-order temperature sensor reaches about 63% of its final value after:',97 'option_A': 'Two time constants',98 'option_B': 'Five time constants',99 'option_C': 'One time constant',100 'option_D': 'Ten time constants',101 'answer': 'C',102 'distractor_strength': {'A': 2.4, 'B': 1.5, 'D': 0.9},103 'topic': 'Dynamic response of sensors',104 'subtopic': 'time constant',105 'explanation': 'A first-order system reaches about 63.2% of its final response after one time constant.'},106 {'question_id': 'Q3',107 'question': 'A transducer is best described as a device that:',108 'option_A': 'Changes signal form',109 'option_B': 'Stores signal value',110 'option_C': 'Displays signal value',111 'option_D': 'Delays signal flow',112 'answer': 'A',113 'distractor_strength': {'B': 1.4, 'C': 1.8, 'D': 1.2},114 'topic': 'Sensors and transducers',115 'subtopic': 'transducer function',116 'explanation': 'A transducer converts information from one form into another, often from a physical effect '117 'into an electrical signal.'},118 {'question_id': 'Q9',119 'question': 'A bimetallic strip bends when heated mainly because:',120 'option_A': 'One metal expands more than the other',121 'option_B': 'Both metals expand by exactly equal amounts',122 'option_C': 'One metal becomes electrically charged',123 'option_D': 'Both metals lose all mechanical stiffness',124 'answer': 'A',125 'distractor_strength': {'B': 2.4, 'C': 1.1, 'D': 1.4},126 'topic': 'Temperature sensors',127 'subtopic': 'bimetallic strip',128 'explanation': 'Different thermal expansion rates make the joined metals bend as temperature changes.'},129 {'question_id': 'Q36',130 'question': 'A controller gives fast response but large overshoot. Which action is commonly used to oppose '131 'rapid error change?',132 'option_A': 'Integral action',133 'option_B': 'Derivative action',134 'option_C': 'Offset action',135 'option_D': 'Dead-band action',136 'answer': 'B',137 'distractor_strength': {'A': 2.5, 'C': 1.1, 'D': 1.6},138 'topic': 'PID control',139 'subtopic': 'derivative action',140 'explanation': 'Derivative action responds to the rate of change of error and can reduce overshoot.'},141 {'question_id': 'Q46',142 'question': 'A sensor has excellent resolution but poor repeatability. What does this mean?',143 'option_A': 'It detects no changes but repeats perfectly',144 'option_B': 'It detects small changes but varies on repeats',145 'option_C': 'It has no hysteresis but large dead band',146 'option_D': 'It has high output but no input signal',147 'answer': 'B',148 'distractor_strength': {'A': 2.2, 'C': 1.8, 'D': 1.3},149 'topic': 'Sensor performance terminology',150 'subtopic': 'resolution and repeatability',151 'explanation': 'Resolution and repeatability are different. A sensor may detect fine changes but still '152 'give inconsistent repeated readings.'},153 {'question_id': 'Q30',154 'question': 'A tachogenerator produces voltage proportional to angular velocity. If voltage polarity '155 'reverses, it most directly indicates:',156 'option_A': 'Increase of encoder resolution',157 'option_B': 'Reduction of winding resistance',158 'option_C': 'Loss of all feedback signal',159 'option_D': 'Reversal of rotation direction',160 'answer': 'D',161 'distractor_strength': {'A': 1.5, 'B': 1.2, 'C': 2.0},162 'topic': 'Velocity sensors',163 'subtopic': 'tachogenerator polarity',164 'explanation': 'The polarity of a tachogenerator output can indicate the direction of rotation.'},165 {'question_id': 'Q33',166 'question': 'In a DC motor model, back emf mainly depends on:',167 'option_A': 'Armature current',168 'option_B': 'Angular velocity',169 'option_C': 'Brush resistance only',170 'option_D': 'Gearbox ratio only',171 'answer': 'B',172 'distractor_strength': {'A': 2.4, 'C': 1.4, 'D': 1.7},173 'topic': 'Electromechanical systems',174 'subtopic': 'DC motor back emf',175 'explanation': 'Back emf is proportional to the motor angular velocity.'},176 {'question_id': 'Q7',177 'question': 'In an LVDT, when the core is exactly at the central position, the ideal output is:',178 'option_A': 'Maximum positive voltage',179 'option_B': 'Maximum negative voltage',180 'option_C': 'Nearly zero voltage',181 'option_D': 'Full supply voltage',182 'answer': 'C',183 'distractor_strength': {'A': 2.0, 'B': 2.0, 'D': 1.5},184 'topic': 'Displacement sensors',185 'subtopic': 'LVDT null position',186 'explanation': 'At the null position, the voltages induced in the two secondary coils cancel each other.'},187 {'question_id': 'Q32',188 'question': 'For a mass-spring-damper system with input force F and output displacement x, the transfer '189 'function X(s)/F(s) is:',190 'option_A': 'ms² + cs + k',191 'option_B': 's / (m + c + k)',192 'option_C': 'k / (ms + c)',193 'option_D': '1 / (ms² + cs + k)',194 'answer': 'D',195 'distractor_strength': {'A': 2.7, 'B': 1.4, 'C': 1.9},196 'topic': 'System transfer functions',197 'subtopic': 'mass-spring-damper model',198 'explanation': 'From mẍ + cẋ + kx = F, taking Laplace gives X/F = 1/(ms² + cs + k).'},199 {'question_id': 'Q17',200 'question': 'Cold-junction compensation is needed in thermocouple measurement because:',201 'option_A': 'The hot junction gives zero emf',202 'option_B': 'The metal wires act as switches',203 'option_C': 'The display creates the temperature',204 'option_D': 'The reference junction affects the emf',205 'answer': 'D',206 'distractor_strength': {'A': 2.0, 'B': 1.0, 'C': 1.2},207 'topic': 'Thermocouples',208 'subtopic': 'cold-junction compensation',209 'explanation': 'Thermocouple voltage depends on the temperature difference between hot and reference '210 'junctions.'},211 {'question_id': 'Q5',212 'question': 'In an open-loop room heating system, which feature is normally absent?',213 'option_A': 'Heating element',214 'option_B': 'Power switching',215 'option_C': 'Temperature feedback',216 'option_D': 'Input command',217 'answer': 'C',218 'distractor_strength': {'A': 1.6, 'B': 1.9, 'D': 2.2},219 'topic': 'Open-loop and closed-loop control',220 'subtopic': 'open-loop heating',221 'explanation': 'An open-loop system acts without using measured output feedback for correction.'},222 {'question_id': 'Q16',223 'question': 'In a DC motor speed control system, a tachogenerator is mainly used to:',224 'option_A': 'Increase shaft friction',225 'option_B': 'Store motor position',226 'option_C': 'Feed back motor speed',227 'option_D': 'Limit armature length',228 'answer': 'C',229 'distractor_strength': {'A': 1.2, 'B': 2.1, 'D': 0.8},230 'topic': 'Closed-loop speed control',231 'subtopic': 'tachogenerator feedback',232 'explanation': 'A tachogenerator produces a voltage related to angular speed and is used for feedback.'},233 {'question_id': 'Q42',234 'question': 'A motor speed loop uses tachogenerator feedback. The tachogenerator polarity is accidentally '235 'reversed. What is the likely result?',236 'option_A': 'The system becomes more stable',237 'option_B': 'Negative feedback may become positive feedback, causing runaway or instability',238 'option_C': 'The motor cannot receive voltage',239 'option_D': 'The ADC becomes 16-bit automatically',240 'answer': 'B',241 'distractor_strength': {'A': 2.2, 'C': 1.3, 'D': 0.8},242 'topic': 'Feedback fault diagnosis',243 'subtopic': 'feedback polarity',244 'explanation': 'Wrong feedback polarity can make the controller increase the error instead of reducing '245 'it.'},246 {'question_id': 'Q10',247 'question': 'A sensor has high sensitivity. Which statement is most accurate?',248 'option_A': 'Large input change gives no output change',249 'option_B': 'Small input change gives large output change',250 'option_C': 'Small output change gives large input change',251 'option_D': 'Output stays fixed for all input changes',252 'answer': 'B',253 'distractor_strength': {'A': 1.7, 'C': 2.7, 'D': 1.2},254 'topic': 'Sensor performance',255 'subtopic': 'sensitivity',256 'explanation': 'Sensitivity is the ratio of output change to input change.'},257 {'question_id': 'Q24',258 'question': 'In a sampled data system, sampling too slowly can cause:',259 'option_A': 'Hysteresis',260 'option_B': 'Dead band',261 'option_C': 'Aliasing',262 'option_D': 'Saturation',263 'answer': 'C',264 'distractor_strength': {'A': 1.8, 'B': 1.5, 'D': 1.6},265 'topic': 'Data acquisition',266 'subtopic': 'sampling',267 'explanation': 'Aliasing occurs when the sampling rate is too low for the signal being measured.'},268 {'question_id': 'Q12',269 'question': 'Mechanical switch bounce can cause a microprocessor to:',270 'option_A': 'Read analog voltage as temperature',271 'option_B': 'Convert input current into pressure',272 'option_C': 'Store the switch force permanently',273 'option_D': 'Count one press as many presses',274 'answer': 'D',275 'distractor_strength': {'A': 1.5, 'B': 1.1, 'C': 0.9},276 'topic': 'Switch debouncing',277 'subtopic': 'mechanical bounce',278 'explanation': 'Switch bounce produces rapid unwanted transitions before the signal settles.'},279 {'question_id': 'Q31',280 'question': 'In sequential control of a washing machine, why are water-level and door-position feedback '281 'important?',282 'option_A': 'They make every operation run at once',283 'option_B': 'They confirm safe conditions before the next step',284 'option_C': 'They remove the need for a controller',285 'option_D': 'They replace the motor and heater',286 'answer': 'B',287 'distractor_strength': {'A': 1.3, 'C': 1.7, 'D': 1.5},288 'topic': 'Sequential control',289 'subtopic': 'feedback conditions',290 'explanation': 'Sequential systems depend on feedback conditions before moving to the next operation.'},291 {'question_id': 'Q43',292 'question': 'A temperature control system has accurate sensor reading but poor final product temperature. '293 'Which hidden cause best fits a mechatronic-system view?',294 'option_A': 'Display brightness may not represent the control signal',295 'option_B': 'Switch timing may not represent the supply voltage',296 'option_C': 'Sensor position may not represent the product temperature',297 'option_D': 'Motor current may not represent the sensor reading',298 'answer': 'C',299 'distractor_strength': {'A': 1.7, 'B': 1.5, 'D': 2.0},300 'topic': 'Mechatronic system design',301 'subtopic': 'sensor placement',302 'explanation': 'A sensor may read accurately at its own location but still fail to represent the actual '303 'controlled part of the process.'},304 {'question_id': 'Q37',305 'question': 'A multiplexed data acquisition system samples 8 sensors using one ADC. The main advantage is:',306 'option_A': 'Higher sensor accuracy always',307 'option_B': 'Zero sampling delay always',308 'option_C': 'No conditioning needed',309 'option_D': 'Lower hardware cost',310 'answer': 'D',311 'distractor_strength': {'A': 1.9, 'B': 1.8, 'C': 2.2},312 'topic': 'Data acquisition systems',313 'subtopic': 'multiplexing',314 'explanation': 'Multiplexing lets several input channels share one ADC.'},315 {'question_id': 'Q48',316 'question': 'A machine uses an encoder for position. After power loss, the controller no longer knows the '317 'shaft angle unless homing is performed. Which encoder type is most likely used?',318 'option_A': 'Absolute encoder',319 'option_B': 'Incremental encoder',320 'option_C': 'Thermocouple',321 'option_D': 'LVDT with no core',322 'answer': 'B',323 'distractor_strength': {'A': 2.8, 'C': 0.7, 'D': 1.1},324 'topic': 'Optical encoders',325 'subtopic': 'incremental encoder',326 'explanation': 'Incremental encoders count movement but do not inherently store absolute position after '327 'power loss.'},328 {'question_id': 'Q11',329 'question': 'A strain gauge is usually connected in a bridge circuit mainly because:',330 'option_A': 'Its voltage output is naturally large',331 'option_B': 'Its current must always be zero',332 'option_C': 'Its resistance change is very small',333 'option_D': 'Its strain value is already digital',334 'answer': 'C',335 'distractor_strength': {'A': 1.9, 'B': 1.0, 'D': 1.3},336 'topic': 'Strain gauges',337 'subtopic': 'bridge circuit',338 'explanation': 'A bridge circuit helps detect small resistance changes caused by strain.'},339 {'question_id': 'Q22',340 'question': 'A proportional controller alone may leave steady-state error because:',341 'option_A': 'It stores all previous error values',342 'option_B': 'A nonzero error may be needed for output',343 'option_C': 'It reacts only to error acceleration',344 'option_D': 'It removes feedback from the loop',345 'answer': 'B',346 'distractor_strength': {'A': 2.0, 'C': 1.7, 'D': 1.2},347 'topic': 'Controllers',348 'subtopic': 'proportional control',349 'explanation': 'Pure proportional action often requires some remaining error to produce the needed control '350 'effort.'},351 {'question_id': 'Q49',352 'question': 'A pneumatic actuator is fast but stops at slightly different positions under changing load. '353 'Which explanation is strongest?',354 'option_A': 'Pneumatic systems have no pressure',355 'option_B': 'Compressed air is perfectly rigid',356 'option_C': 'The actuator has infinite stiffness',357 'option_D': 'Air compressibility and load variation reduce precise positioning',358 'answer': 'D',359 'distractor_strength': {'A': 0.8, 'B': 2.2, 'C': 1.8},360 'topic': 'Pneumatic actuation',361 'subtopic': 'compressibility',362 'explanation': 'Compressed air is compressible, so position accuracy can suffer under load changes.'},363 {'question_id': 'Q38',364 'question': 'In a bridge measurement circuit, differential amplification is useful because it:',365 'option_A': 'Amplifies supply voltage and rejects sensor signal',366 'option_B': 'Amplifies resistance and rejects mechanical strain',367 'option_C': 'Amplifies difference and rejects common-mode signal',368 'option_D': 'Amplifies temperature and rejects bridge balance',369 'answer': 'C',370 'distractor_strength': {'A': 2.2, 'B': 1.8, 'D': 1.6},371 'topic': 'Signal conditioning',372 'subtopic': 'differential amplification',373 'explanation': 'Differential amplifiers are useful for small bridge outputs that contain common-mode '374 'voltage.'},375 {'question_id': 'Q20',376 'question': 'In an LVDT with phase-sensitive demodulation, the output phase is useful because it '377 'indicates:',378 'option_A': 'Temperature of the winding',379 'option_B': 'Resistance of the core',380 'option_C': 'Direction of core movement',381 'option_D': 'Frequency of the supply only',382 'answer': 'C',383 'distractor_strength': {'A': 1.2, 'B': 1.5, 'D': 2.0},384 'topic': 'LVDT signal conditioning',385 'subtopic': 'phase-sensitive demodulation',386 'explanation': 'The phase changes depending on which side of the null position the core moves.'},387 {'question_id': 'Q13',388 'question': 'A Schmitt trigger is added before a microprocessor input. What is the main purpose?',389 'option_A': 'To increase actuator output torque',390 'option_B': 'To convert pressure into motion',391 'option_C': 'To sharpen noisy digital transitions',392 'option_D': 'To store sensor data permanently',393 'answer': 'C',394 'distractor_strength': {'A': 1.4, 'B': 1.1, 'D': 1.6},395 'topic': 'Digital input conditioning',396 'subtopic': 'Schmitt trigger',397 'explanation': 'A Schmitt trigger uses hysteresis to produce clean digital switching from noisy or slowly '398 'changing input signals.'},399 {'question_id': 'Q28',400 'question': 'In a mass-spring-damper model, increasing damping usually causes:',401 'option_A': 'Higher overshoot tendency',402 'option_B': 'Lower oscillation tendency',403 'option_C': 'Larger steady vibration',404 'option_D': 'Smaller spring stiffness',405 'answer': 'B',406 'distractor_strength': {'A': 2.5, 'C': 1.9, 'D': 1.3},407 'topic': 'Dynamic response',408 'subtopic': 'damping',409 'explanation': 'Damping dissipates energy and reduces oscillatory behavior.'},410 {'question_id': 'Q1',411 'question': 'In a basic mechatronic measurement system, what is the main role of a sensor?',412 'option_A': 'To show the final reading',413 'option_B': 'To sense the input variable',414 'option_C': 'To amplify the output signal',415 'option_D': 'To drive the final actuator',416 'answer': 'B',417 'distractor_strength': {'A': 2.1, 'C': 2.4, 'D': 1.6},418 'topic': 'Sensors and transducers',419 'subtopic': 'sensor function',420 'explanation': 'A sensor responds to the input variable being measured and provides information for the '421 'rest of the system.'},422 {'question_id': 'Q6',423 'question': 'A sensor gives the same output every time the same input is applied. Which performance term '424 'best describes this property?',425 'option_A': 'Sensitivity',426 'option_B': 'Repeatability',427 'option_C': 'Resolution',428 'option_D': 'Linearity',429 'answer': 'B',430 'distractor_strength': {'A': 2.2, 'C': 2.5, 'D': 1.9},431 'topic': 'Sensor performance terminology',432 'subtopic': 'repeatability',433 'explanation': 'Repeatability means the sensor gives nearly the same output for repeated identical input '434 'conditions.'},435 {'question_id': 'Q15',436 'question': 'A 10-bit ADC is used over a 0–5 V range. The approximate voltage resolution is:',437 'option_A': '5.00 V',438 'option_B': '4.88 mV',439 'option_C': '0.50 V',440 'option_D': '10.0 mV',441 'answer': 'B',442 'distractor_strength': {'A': 0.8, 'C': 1.6, 'D': 2.4},443 'topic': 'Data acquisition',444 'subtopic': 'ADC resolution',445 'explanation': 'Resolution ≈ 5/1024 = 0.00488 V = 4.88 mV.'},446 {'question_id': 'Q26',447 'question': 'A hydraulic cylinder has piston area 0.002 m² and pressure 5 MPa. Ignoring losses, the force '448 'produced is:',449 'option_A': '100 N',450 'option_B': '1000 N',451 'option_C': '10000 N',452 'option_D': '25000 N',453 'answer': 'C',454 'distractor_strength': {'A': 0.9, 'B': 2.0, 'D': 2.2},455 'topic': 'Hydraulic actuation',456 'subtopic': 'pressure-force relation',457 'explanation': 'Force = pressure × area = 5,000,000 × 0.002 = 10,000 N.'},458 {'question_id': 'Q34',459 'question': 'In frequency response analysis, a small phase margin usually means the closed-loop system is:',460 'option_A': 'Closer to oscillation or instability',461 'option_B': 'Always slower but more stable',462 'option_C': 'Independent of loop gain',463 'option_D': 'Free from time delay effects',464 'answer': 'A',465 'distractor_strength': {'B': 2.1, 'C': 1.4, 'D': 1.8},466 'topic': 'Frequency response',467 'subtopic': 'phase margin',468 'explanation': 'Low phase margin means the system is closer to the boundary of instability.'},469 {'question_id': 'Q50',470 'question': 'A complete mechatronic product works in isolated tests: sensor test passes, actuator test '471 'passes, controller code passes. But the integrated system fails intermittently. What is the '472 'best systems explanation?',473 'option_A': 'Interfaces were not tested together',474 'option_B': 'Each part must work perfectly',475 'option_C': 'Mechanical parts cannot affect code',476 'option_D': 'Software cannot affect hardware',477 'answer': 'A',478 'distractor_strength': {'B': 2.1, 'C': 1.8, 'D': 1.7},479 'topic': 'Mechatronic system integration',480 'subtopic': 'subsystem interaction',481 'explanation': 'A full mechatronic system can fail because of timing, noise, loading, interface mismatch, '482 'or subsystem interaction.'},483 {'question_id': 'Q39',484 'question': 'A closed-loop system becomes unstable after proportional gain is increased too much. The most '485 'likely reason is:',486 'option_A': 'Loop gain and phase lag reinforce oscillation',487 'option_B': 'Feedback automatically becomes zero',488 'option_C': 'Reference input removes the error',489 'option_D': 'The actuator stops receiving power',490 'answer': 'A',491 'distractor_strength': {'B': 1.2, 'C': 1.5, 'D': 1.0},492 'topic': 'Closed-loop stability',493 'subtopic': 'gain and phase lag',494 'explanation': 'High loop gain with enough phase lag can make a feedback system oscillate.'},495 {'question_id': 'Q45',496 'question': 'A PLC-controlled press must not operate unless two guard switches are closed. Which logic is '497 'safest for the enable condition?',498 'option_A': 'Guard1 AND Guard2',499 'option_B': 'Guard1 OR Guard2',500 'option_C': 'NOT Guard1',501 'option_D': 'Guard1 XOR Guard2',502 'answer': 'A',503 'distractor_strength': {'B': 2.4, 'C': 1.2, 'D': 2.0},504 'topic': 'PLC safety logic',505 'subtopic': 'AND condition',506 'explanation': 'Both guard switches must be true, so AND logic is required.'},507 {'question_id': 'Q2',508 'question': 'Which type of control system compares the actual output with the desired output?',509 'option_A': 'Open-loop control system',510 'option_B': 'Closed-loop control system',511 'option_C': 'Sequential timing system',512 'option_D': 'Manual switching system',513 'answer': 'B',514 'distractor_strength': {'A': 2.6, 'C': 1.5, 'D': 1.1},515 'topic': 'Control systems',516 'subtopic': 'closed-loop feedback',517 'explanation': 'A closed-loop system uses feedback to compare the measured output with the required '518 'value.'},519 {'question_id': 'Q25',520 'question': 'A stepper motor has 200 full steps per revolution and a 5:1 reduction gearbox. What is the '521 'output shaft step angle?',522 'option_A': '1.8°',523 'option_B': '0.36°',524 'option_C': '5.0°',525 'option_D': '9.0°',526 'answer': 'B',527 'distractor_strength': {'A': 2.8, 'C': 1.2, 'D': 1.0},528 'topic': 'Electrical actuation',529 'subtopic': 'stepper motor resolution',530 'explanation': 'Motor step = 360/200 = 1.8°. With 5:1 reduction, output step = 1.8/5 = 0.36°.'},531 {'question_id': 'Q40',532 'question': 'A digital controller receives a noisy analog signal directly without filtering or protection. '533 'Which failure is most likely?',534 'option_A': 'Perfect control accuracy',535 'option_B': 'Automatic increase in sensor range',536 'option_C': 'Zero quantization error',537 'option_D': 'False or unstable control decisions',538 'answer': 'D',539 'distractor_strength': {'A': 0.9, 'B': 1.1, 'C': 1.7},540 'topic': 'Microprocessor interfacing',541 'subtopic': 'input signal protection',542 'explanation': 'Noisy input can cause the controller to act on incorrect measurements.'},543 {'question_id': 'Q44',544 'question': 'A sampled controller is stable in simulation with continuous equations but oscillates in '545 'hardware. Which overlooked factor is most suspicious?',546 'option_A': 'The existence of a reference input',547 'option_B': 'The use of a mechanical load',548 'option_C': 'The presence of a power supply',549 'option_D': 'Sampling delay and zero-order hold effects',550 'answer': 'D',551 'distractor_strength': {'A': 1.6, 'B': 2.0, 'C': 1.2},552 'topic': 'Digital control',553 'subtopic': 'sampling delay',554 'explanation': 'Real digital controllers introduce sampling and hold delays that affect stability.'},555 {'question_id': 'Q35',556 'question': 'A sensor has nonlinearity ±0.5% full scale and full-scale output 10 V. The maximum '557 'nonlinearity error is:',558 'option_A': '0.005 V',559 'option_B': '0.5 V',560 'option_C': '0.05 V',561 'option_D': '5 V',562 'answer': 'C',563 'distractor_strength': {'A': 2.3, 'B': 2.0, 'D': 0.9},564 'topic': 'Sensor error analysis',565 'subtopic': 'nonlinearity error',566 'explanation': '0.5% of 10 V = 0.005 × 10 = 0.05 V.'},567 {'question_id': 'Q8',568 'question': 'Why is Gray code often used in absolute optical encoders?',569 'option_A': 'It increases shaft speed',570 'option_B': 'It removes all sensor noise',571 'option_C': 'It changes one bit at a time',572 'option_D': 'It gives analog output directly',573 'answer': 'C',574 'distractor_strength': {'A': 1.1, 'B': 1.8, 'D': 2.1},575 'topic': 'Optical encoders',576 'subtopic': 'Gray code',577 'explanation': 'Gray code reduces transition error because only one bit changes between adjacent '578 'positions.'},579 {'question_id': 'Q29',580 'question': 'A Schmitt trigger is useful in switch interfacing because it:',581 'option_A': 'Provides gain for hydraulic pressure',582 'option_B': 'Provides torque for motor starting',583 'option_C': 'Provides hysteresis for clean switching',584 'option_D': 'Provides storage for analog force',585 'answer': 'C',586 'distractor_strength': {'A': 1.1, 'B': 1.0, 'D': 1.3},587 'topic': 'Digital input conditioning',588 'subtopic': 'hysteresis',589 'explanation': 'A Schmitt trigger reduces false switching from noise or bouncing by using two threshold '590 'levels.'},591 {'question_id': 'Q41',592 'question': 'A robotic gripper uses a force sensor. The measured force slowly drifts upward even when no '593 'object is held. The controller starts opening the gripper unnecessarily. What is the deepest '594 'design issue?',595 'option_A': 'The gripper has too many fingers',596 'option_B': 'The system lacks drift compensation or periodic zero calibration',597 'option_C': 'The actuator is definitely too weak',598 'option_D': 'The ADC resolution is always infinite',599 'answer': 'B',600 'distractor_strength': {'A': 0.9, 'C': 1.7, 'D': 1.1},601 'topic': 'Sensor fault and calibration',602 'subtopic': 'sensor drift',603 'explanation': 'Sensor drift can mislead feedback control unless calibration or compensation is included.'},604 {'question_id': 'Q27',605 'question': 'In PLC operation, the scan cycle usually means the PLC repeatedly:',606 'option_A': 'Reads inputs, solves logic, updates outputs',607 'option_B': 'Updates outputs, ignores inputs, stops logic',608 'option_C': 'Reads memory, clears program, blocks outputs',609 'option_D': 'Starts motor, resets timer, deletes counter',610 'answer': 'A',611 'distractor_strength': {'B': 2.0, 'C': 1.4, 'D': 1.1},612 'topic': 'Programmable logic controllers',613 'subtopic': 'PLC scan cycle',614 'explanation': 'A PLC scan commonly reads inputs, executes the program, and updates outputs.'},615 {'question_id': 'Q21',616 'question': 'A low-pass filter in signal conditioning is mainly used to:',617 'option_A': 'Reduce high-frequency noise',618 'option_B': 'Remove low-frequency signal',619 'option_C': 'Increase all frequency bands',620 'option_D': 'Convert frequency into force',621 'answer': 'A',622 'distractor_strength': {'B': 2.3, 'C': 1.6, 'D': 0.9},623 'topic': 'Filtering',624 'subtopic': 'low-pass filter',625 'explanation': 'A low-pass filter passes low-frequency components and attenuates high-frequency noise.'}]626 627 628# Inherent difficulty stays on the teacher-authored scale from the pasted629# mechatronics bank. Do not infer difficulty from list order.630ASSUMED_INHERENT_DIFFICULTY = {'Q1': 1.2,631 'Q2': 1.8,632 'Q3': 2.1,633 'Q4': 2.5,634 'Q5': 2.9,635 'Q6': 3.1,636 'Q7': 3.4,637 'Q8': 3.7,638 'Q9': 3.9,639 'Q10': 4.7,640 'Q11': 4.4,641 'Q12': 4.6,642 'Q13': 4.9,643 'Q14': 5.1,644 'Q15': 5.3,645 'Q16': 5.5,646 'Q17': 5.7,647 'Q18': 5.9,648 'Q19': 6.1,649 'Q20': 6.2,650 'Q21': 6.4,651 'Q22': 6.5,652 'Q23': 6.7,653 'Q24': 6.9,654 'Q25': 7.1,655 'Q26': 7.2,656 'Q27': 7.3,657 'Q28': 7.4,658 'Q29': 7.6,659 'Q30': 7.8,660 'Q31': 7.9,661 'Q32': 8.1,662 'Q33': 8.2,663 'Q34': 8.3,664 'Q35': 8.4,665 'Q36': 8.5,666 'Q37': 8.6,667 'Q38': 8.7,668 'Q39': 8.8,669 'Q40': 8.9,670 'Q41': 9.1,671 'Q42': 9.2,672 'Q43': 9.3,673 'Q44': 9.4,674 'Q45': 9.5,675 'Q46': 9.6,676 'Q47': 9.7,677 'Q48': 9.8,678 'Q49': 9.9,679 'Q50': 10.0}680 681 682# ---------------------------------------------------------------------683# 3. Utility functions684# ---------------------------------------------------------------------685 686def sigmoid(x: float) -> float:687 return 1.0 / (1.0 + math.exp(-x))688 689 690def clip(value: float, low: float, high: float) -> float:691 return float(max(low, min(high, value)))692 693 694def ability_to_10_scale(ability: int | float) -> float:695 """696 Convert ability from 10-30 scale into 0-10 scale.697 698 Why?699 - This experiment is designed for students with ability 10-30.700 - This scale is kept for reporting/backward compatibility.701 - Do not use this for direct comparison with perceived difficulty.702 """703 ability = clip(float(ability), MIN_ABILITY, MAX_ABILITY)704 return ((ability - MIN_ABILITY) / (MAX_ABILITY - MIN_ABILITY)) * 10.0705 706 707def ability_to_difficulty_scale(ability: int | float) -> float:708 """709 Convert ability from 10-30 scale into the perceived-difficulty scale.710 711 Perceived difficulty is sampled from DIFFICULTY_LOW..DIFFICULTY_HIGH,712 currently 0..15. This is the scale used for answer probability and713 response-time simulation.714 """715 ability = clip(float(ability), MIN_ABILITY, MAX_ABILITY)716 ability_norm = (ability - MIN_ABILITY) / (MAX_ABILITY - MIN_ABILITY)717 return DIFFICULTY_LOW + ability_norm * (DIFFICULTY_HIGH - DIFFICULTY_LOW)718 719 720def make_default_distractor_strength(correct_answer: str) -> dict[str, float]:721 """722 If you do not manually give distractor strengths,723 all wrong options are treated as equally attractive.724 """725 return {option: 1.0 for option in OPTIONS if option != correct_answer}726 727 728# ---------------------------------------------------------------------729# 4. Build difficulty profile for ability 10-30730# ---------------------------------------------------------------------731 732def build_difficulty_profile(inherent_difficulty: float) -> dict[int, dict[str, float]]:733 """734 Creates:735 {736 10: {"center": ..., "spread": ...},737 11: {"center": ..., "spread": ...},738 ...739 30: {"center": ..., "spread": ...},740 }741 742 center:743 Most likely perceived difficulty for that ability.744 745 spread:746 How much the sampled difficulty varies around the center.747 748 Important:749 In this experiment, ability=10 is the weakest supported student and750 ability=30 is the strongest. The model is not trained for absolute751 beginner behavior below ability 10.752 """753 754 # Mechatronics questions contain more technical terminology and multistep755 # reasoning, so perceived difficulty should climb more sharply for hard756 # questions while still preserving separation among easier questions.757 high_difficulty_excess = max(0.0, inherent_difficulty - 7.0)758 low_ability_center = clip(759 1.24 * inherent_difficulty + 1.15 + 0.55 * high_difficulty_excess + 0.10 * high_difficulty_excess**2,760 1.2,761 14.8,762 )763 high_ability_center = clip(764 0.52 * inherent_difficulty + 0.55 + 0.24 * high_difficulty_excess,765 0.8,766 7.4,767 )768 769 profile: dict[int, dict[str, float]] = {}770 771 for ability in range(MIN_ABILITY, MAX_ABILITY + 1):772 t = (ability - MIN_ABILITY) / (MAX_ABILITY - MIN_ABILITY)773 774 # Linear interpolation from low ability center to high ability center.775 center = low_ability_center * (1.0 - t) + high_ability_center * t776 777 # Hard technical questions should have a little more uncertainty in778 # perceived difficulty, because students can know one sub-concept but779 # still struggle with another.780 spread = 0.32 + 0.045 * inherent_difficulty + 0.035 * high_difficulty_excess + 0.018 * abs(center - 6.0)781 spread = clip(spread, 0.32, 1.12)782 783 profile[ability] = {784 "center": round(center, 2),785 "spread": round(spread, 2),786 }787 788 return profile789 790 791def estimate_base_time(question: dict[str, Any], inherent_difficulty: float) -> float:792 """793 Assumed base response time in seconds.794 795 Higher difficulty + longer text => more base time.796 """797 text = (798 question["question"]799 + " "800 + question["option_A"]801 + " "802 + question["option_B"]803 + " "804 + question["option_C"]805 + " "806 + question["option_D"]807 )808 809 length_factor = min(len(text) / 70.0, 10.0)810 high_difficulty_excess = max(0.0, inherent_difficulty - 7.0)811 high_difficulty_time = 4.0 * (high_difficulty_excess ** 1.45)812 advanced_reasoning_time = 2.0 * (max(0.0, inherent_difficulty - 8.5) ** 2)813 base_time = (814 12.0815 + inherent_difficulty * 4.1816 + length_factor817 + high_difficulty_time818 + advanced_reasoning_time819 )820 821 return round(base_time, 2)822 823 824def attach_assumed_question_metadata(raw_questions: list[dict[str, Any]]) -> list[dict[str, Any]]:825 """826 Adds:827 - inherent_difficulty828 - base_time829 - difficulty_profile830 - distractor_strength831 """832 questions = deepcopy(raw_questions)833 834 for question in questions:835 qid = question["question_id"]836 837 inherent_difficulty = ASSUMED_INHERENT_DIFFICULTY[qid]838 839 question["inherent_difficulty"] = inherent_difficulty840 question["base_time"] = estimate_base_time(question, inherent_difficulty)841 question["difficulty_profile"] = build_difficulty_profile(inherent_difficulty)842 843 if "distractor_strength" not in question:844 question["distractor_strength"] = make_default_distractor_strength(question["answer"])845 846 return questions847 848 849# This is the final question list you should use in simulation.850QUESTIONS_WITH_METADATA = attach_assumed_question_metadata(QUESTIONS)851 852 853# ---------------------------------------------------------------------854# 5. Difficulty sampling855# ---------------------------------------------------------------------856 857def sample_difficulty_from_distribution(858 center: float,859 spread: float,860 rng: np.random.Generator,861 low: float = DIFFICULTY_LOW,862 high: float = DIFFICULTY_HIGH,863 step: float = 0.1,864) -> float:865 """866 Discrete bell-shaped distribution.867 868 Highest probability is near 'center'.869 Probability decreases on both left and right sides.870 """871 values = np.round(np.arange(low, high + step, step), 2)872 873 weights = np.exp(-0.5 * ((values - center) / spread) ** 2)874 probabilities = weights / weights.sum()875 876 sampled = rng.choice(values, p=probabilities)877 return float(sampled)878 879 880def sample_perceived_difficulty(881 student: dict[str, Any],882 question: dict[str, Any],883 rng: np.random.Generator,884) -> float:885 """886 Picks the difficulty profile row for the student's ability,887 then samples one perceived difficulty value.888 """889 ability = int(student["ability"])890 891 if ability < MIN_ABILITY or ability > MAX_ABILITY:892 raise ValueError(f"Student ability must be between {MIN_ABILITY} and {MAX_ABILITY}. Got {ability}.")893 894 params = question["difficulty_profile"][ability]895 center = params["center"]896 spread = params["spread"]897 898 return sample_difficulty_from_distribution(center=center, spread=spread, rng=rng)899 900 901# ---------------------------------------------------------------------902# 6. Option distribution903# ---------------------------------------------------------------------904 905def get_option_distribution(906 student: dict[str, Any],907 question: dict[str, Any],908 perceived_difficulty: float,909) -> dict[str, float]:910 """911 Returns probability of choosing each option.912 913 Example:914 {915 "A": 0.70,916 "B": 0.10,917 "C": 0.12,918 "D": 0.08,919 }920 """921 922 ability_difficulty_scale = ability_to_difficulty_scale(student["ability"])923 correct = question["answer"]924 925 # If ability on the same scale is greater than perceived difficulty,926 # mastery becomes high.927 mastery = sigmoid(0.85 * (ability_difficulty_scale - perceived_difficulty))928 929 # Random guessing floor for 4-option MCQ.930 min_correct = 1.0 / len(OPTIONS)931 932 # Even a strong student can make careless mistakes.933 carelessness = float(student.get("carelessness", 0.05))934 max_correct = clip(0.98 - carelessness, min_correct, 0.98)935 936 p_correct = min_correct + (max_correct - min_correct) * mastery937 p_correct = clip(p_correct, min_correct, max_correct)938 939 distribution: dict[str, float] = {}940 distribution[correct] = p_correct941 942 wrong_probability = 1.0 - p_correct943 944 distractors = question.get("distractor_strength") or make_default_distractor_strength(correct)945 946 # Safety: only wrong options should be in distractors.947 distractors = {opt: strength for opt, strength in distractors.items() if opt != correct}948 949 total_strength = sum(distractors.values())950 951 if total_strength <= 0:952 distractors = make_default_distractor_strength(correct)953 total_strength = sum(distractors.values())954 955 for option, strength in distractors.items():956 distribution[option] = wrong_probability * (strength / total_strength)957 958 # Ensure all options exist.959 for option in OPTIONS:960 distribution.setdefault(option, 0.0)961 962 # Normalize for numerical safety.963 total = sum(distribution.values())964 distribution = {option: prob / total for option, prob in distribution.items()}965 966 return distribution967 968 969# ---------------------------------------------------------------------970# 7. Response time sampling971# ---------------------------------------------------------------------972 973def sample_response_time(974 student: dict[str, Any],975 question: dict[str, Any],976 perceived_difficulty: float,977 rng: np.random.Generator,978) -> float:979 """980 Generates different time every time, even for same student + same question.981 982 Uses same sampled perceived_difficulty that was used for answer probability.983 """984 985 base_time = float(question["base_time"])986 ability_difficulty_scale = ability_to_difficulty_scale(student["ability"])987 988 time_multiplier = float(student.get("time_multiplier", 1.0))989 990 difficulty_gap = perceived_difficulty - ability_difficulty_scale991 high_difficulty_excess = max(0.0, float(question["inherent_difficulty"]) - 7.0)992 993 # Technical struggle has a stronger effect on response time than in the994 # English grammar simulator.995 struggle_factor = 1.0 + max(0.0, difficulty_gap) * 0.125 + high_difficulty_excess * 0.045996 997 # Even strong students still need reading/reasoning time for technical MCQ.998 ease_factor = 1.0 - min(max(0.0, ability_difficulty_scale - perceived_difficulty) * 0.014, 0.20)999 1000 min_time = max(3.0, base_time * time_multiplier * 0.38 * ease_factor)1001 max_time = max(1002 min_time + 2.0,1003 base_time * time_multiplier * (2.10 + high_difficulty_excess * 0.12) * struggle_factor,1004 )1005 1006 alpha = float(student.get("time_alpha", 2.5))1007 beta = float(student.get("time_beta", 4.0))1008 1009 x = rng.beta(alpha, beta)1010 1011 time_taken = min_time + x * (max_time - min_time)1012 1013 return round(float(time_taken), 2)1014 1015 1016# ---------------------------------------------------------------------1017# 8. Student creation1018# ---------------------------------------------------------------------1019 1020def create_student(1021 student_id: str,1022 ability: int,1023 carelessness: float | None = None,1024 time_multiplier: float | None = None,1025 time_alpha: float | None = None,1026 time_beta: float | None = None,1027) -> dict[str, Any]:1028 """1029 Create one simulated student.1030 1031 ability:1032 10 = weakest supported student in this experiment1033 30 = strongest supported student in this experiment1034 """1035 1036 if ability < MIN_ABILITY or ability > MAX_ABILITY:1037 raise ValueError(f"ability must be between {MIN_ABILITY} and {MAX_ABILITY}")1038 1039 ability_10 = ability_to_10_scale(ability)1040 ability_difficulty_scale = ability_to_difficulty_scale(ability)1041 1042 if carelessness is None:1043 # Stronger students are usually less careless, but not zero.1044 carelessness = clip(0.16 - ability_10 * 0.011, 0.025, 0.16)1045 1046 if time_multiplier is None:1047 # Stronger students are usually faster.1048 time_multiplier = clip(1.35 - ability_10 * 0.055, 0.65, 1.35)1049 1050 if time_alpha is None:1051 # Lower alpha / higher beta means values tend to be closer to min_time.1052 time_alpha = clip(2.2 + ability_10 * 0.06, 2.0, 3.0)1053 1054 if time_beta is None:1055 time_beta = clip(3.2 + ability_10 * 0.10, 3.0, 4.5)1056 1057 return {1058 "student_id": student_id,1059 "ability": int(ability),1060 "ability_10": round(ability_10, 2),1061 "ability_difficulty_scale": round(ability_difficulty_scale, 2),1062 "carelessness": round(float(carelessness), 3),1063 "time_multiplier": round(float(time_multiplier), 3),1064 "time_alpha": round(float(time_alpha), 3),1065 "time_beta": round(float(time_beta), 3),1066 }1067 1068 1069def create_student_population(1070 variants_per_ability: int = 1,1071 seed: int | None = None,1072 student_id_prefix: str = "S",1073) -> list[dict[str, Any]]:1074 """Create varied student profiles across the supported ability range.1075 1076 Ability controls the main knowledge level. Variants add small differences in1077 carelessness and speed so the model does not see only one personality for a1078 given ability.1079 """1080 1081 if variants_per_ability < 1:1082 raise ValueError("variants_per_ability must be at least 1")1083 1084 rng = np.random.default_rng(seed)1085 population: list[dict[str, Any]] = []1086 1087 for ability in range(MIN_ABILITY, MAX_ABILITY + 1):1088 base = create_student(f"{student_id_prefix}{ability:02d}", ability=ability)1089 1090 if variants_per_ability == 1:1091 population.append(base)1092 continue1093 1094 for variant in range(1, variants_per_ability + 1):1095 carelessness = clip(1096 float(base["carelessness"]) + float(rng.normal(0.0, 0.025)),1097 0.02,1098 0.22,1099 )1100 time_multiplier = clip(1101 float(base["time_multiplier"]) * float(rng.lognormal(mean=0.0, sigma=0.10)),1102 0.55,1103 1.65,1104 )1105 time_alpha = clip(1106 float(base["time_alpha"]) + float(rng.normal(0.0, 0.12)),1107 1.7,1108 3.4,1109 )1110 time_beta = clip(1111 float(base["time_beta"]) + float(rng.normal(0.0, 0.15)),1112 2.5,1113 5.0,1114 )1115 1116 population.append(1117 create_student(1118 f"{student_id_prefix}{ability:02d}_v{variant:02d}",1119 ability=ability,1120 carelessness=carelessness,1121 time_multiplier=time_multiplier,1122 time_alpha=time_alpha,1123 time_beta=time_beta,1124 )1125 )1126 1127 return population1128 1129 1130# ---------------------------------------------------------------------1131# 9. Simulate answer1132# ---------------------------------------------------------------------1133 1134def simulate_answer(1135 student: dict[str, Any],1136 question: dict[str, Any],1137 rng: np.random.Generator | None = None,1138) -> dict[str, Any]:1139 """1140 Simulates one student answering one question.1141 """1142 1143 if rng is None:1144 rng = np.random.default_rng()1145 1146 perceived_difficulty = sample_perceived_difficulty(student, question, rng)1147 1148 option_distribution = get_option_distribution(1149 student=student,1150 question=question,1151 perceived_difficulty=perceived_difficulty,1152 )1153 1154 probs = [option_distribution[opt] for opt in OPTIONS]1155 1156 chosen_option = str(rng.choice(OPTIONS, p=probs))1157 time_taken = sample_response_time(1158 student=student,1159 question=question,1160 perceived_difficulty=perceived_difficulty,1161 rng=rng,1162 )1163 1164 is_correct = chosen_option == question["answer"]1165 1166 return {1167 "student_id": student["student_id"],1168 "student_ability": student["ability"],1169 "student_ability_10": student.get("ability_10", ability_to_10_scale(student["ability"])),1170 "student_ability_difficulty_scale": student.get(1171 "ability_difficulty_scale",1172 ability_to_difficulty_scale(student["ability"]),1173 ),1174 1175 "question_id": question["question_id"],1176 "question": question["question"],1177 "topic": question["topic"],1178 "subtopic": question["subtopic"],1179 1180 "inherent_difficulty": question["inherent_difficulty"],1181 "base_time": question["base_time"],1182 "sampled_perceived_difficulty": perceived_difficulty,1183 1184 "chosen_option": chosen_option,1185 "correct_answer": question["answer"],1186 "is_correct": is_correct,1187 "time_taken": time_taken,1188 1189 "option_distribution": option_distribution,1190 }1191 1192 1193def simulate_dataset(1194 students: list[dict[str, Any]],1195 questions: list[dict[str, Any]],1196 attempts_per_student_question: int = 1,1197 seed: int = 42,1198) -> list[dict[str, Any]]:1199 """1200 Simulate many student-question interactions.