ede2dee772
This commit refactors the organ simulation to enable dynamic interactions between organs, replacing the previous "faked" or hardcoded connections.
Key changes include:
- Major Refactoring: Changed the `Organ::update` method signature to `update(Patient& patient, double deltaTime_s)`, allowing organs to access the shared patient state and other organs. This was propagated to all organ classes.
- Blood Chemistry Model: Introduced a central `Blood` struct in the `Patient` model to track shared resources like oxygen, CO2, glucose, and toxins.
- Organ System Interconnections:
- Lungs & Brain: Lungs now perform gas exchange affecting the blood. The brain consumes O2, produces CO2, and its GCS is affected by hypoxia/hypercapnia.
- Liver-Gallbladder: Gallbladder now receives bile directly from the liver's production rate.
- Digestive System: Stomach passes chyme to the intestines, which absorb glucose into the blood. The pancreas responds to blood glucose changes.
- Renal System: Kidneys' GFR is now influenced by the heart's aortic pressure, and they produce urine that fills the bladder directly.
- Cardiovascular & Neurological: The heart rate responds to hypoxia, and the brain uses live aortic pressure from the heart.
- Comprehensive Test Scenario: Updated the main example to include a 60-second simulation with a meal and a lung injury event to verify the new interconnected system.
This creates a more realistic and scalable physiological simulation framework where organ behaviors are emergent from their interactions.
206 lines
8.7 KiB
C++
206 lines
8.7 KiB
C++
#include "MedicalLib/Heart.h"
|
|
#include "MedicalLib/Patient.h"
|
|
#include <random>
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <cmath>
|
|
#include <numeric>
|
|
#include <deque>
|
|
|
|
// Helper for Gaussian function to model EKG waves
|
|
static double gaussian(double x, double mu, double sigma) {
|
|
return exp(-0.5 * pow((x - mu) / sigma, 2));
|
|
}
|
|
|
|
// Helper function for random fluctuations
|
|
static double getFluctuation(double stddev) {
|
|
static std::random_device rd;
|
|
static std::mt19937 gen(rd());
|
|
std::normal_distribution<> d(0, stddev);
|
|
return d(gen);
|
|
}
|
|
|
|
Heart::Heart(int id, int numLeads)
|
|
: Organ(id, "Heart"),
|
|
heartRate(75.0),
|
|
measuredHeartRate(75.0),
|
|
numLeads(numLeads),
|
|
ekgHistorySize(200),
|
|
totalTime_s(0.0),
|
|
cardiacCyclePosition_s(0.0),
|
|
lastRPeakTime_s(-1.0),
|
|
rPeakDetectedInCycle(false),
|
|
ejectionFraction(0.55) {
|
|
|
|
// Initialize Chambers
|
|
leftAtrium.name = "Left Atrium";
|
|
rightAtrium.name = "Right Atrium";
|
|
leftVentricle.name = "Left Ventricle";
|
|
rightVentricle.name = "Right Ventricle";
|
|
|
|
// Initialize Valves
|
|
mitralValve.name = "Mitral Valve";
|
|
tricuspidValve.name = "Tricuspid Valve";
|
|
aorticValve.name = "Aortic Valve";
|
|
pulmonaryValve.name = "Pulmonary Valve";
|
|
|
|
const std::vector<std::string> allLeadNames = {
|
|
"I", "II", "III", "aVR", "aVL", "aVF",
|
|
"V1", "V2", "V3", "V4", "V5", "V6"
|
|
};
|
|
|
|
int leadsToCreate = std::min((int)allLeadNames.size(), numLeads);
|
|
for (int i = 0; i < leadsToCreate; ++i) {
|
|
leadNames.push_back(allLeadNames[i]);
|
|
ekgData[allLeadNames[i]] = std::deque<double>();
|
|
}
|
|
}
|
|
|
|
void Heart::update(Patient& patient, double deltaTime_s) {
|
|
// --- Electrical Simulation Update ---
|
|
totalTime_s += deltaTime_s;
|
|
|
|
// Compensatory response to hypoxia
|
|
double o2_saturation = patient.blood.oxygenSaturation;
|
|
double targetHeartRate = 75.0;
|
|
if (o2_saturation < 90.0) {
|
|
targetHeartRate = 75.0 + (90.0 - o2_saturation) * 2.0; // Increase HR as SpO2 drops
|
|
}
|
|
|
|
// Move current heart rate towards the target
|
|
heartRate += (targetHeartRate - heartRate) * 0.1 * deltaTime_s;
|
|
heartRate += getFluctuation(0.01); // Slow variation in underlying rate
|
|
heartRate = std::max(60.0, std::min(heartRate, 140.0));
|
|
double cycleDuration_s = 60.0 / heartRate;
|
|
|
|
double oldCyclePosition = cardiacCyclePosition_s;
|
|
cardiacCyclePosition_s += deltaTime_s;
|
|
|
|
// R-peak detection for measured heart rate
|
|
double rPeakCycleTime = 0.22 * cycleDuration_s;
|
|
if (oldCyclePosition < rPeakCycleTime && cardiacCyclePosition_s >= rPeakCycleTime && !rPeakDetectedInCycle) {
|
|
if (lastRPeakTime_s > 0) {
|
|
measuredHeartRate = 60.0 / (totalTime_s - lastRPeakTime_s);
|
|
}
|
|
lastRPeakTime_s = totalTime_s;
|
|
rPeakDetectedInCycle = true;
|
|
}
|
|
|
|
if (cardiacCyclePosition_s > cycleDuration_s) {
|
|
cardiacCyclePosition_s -= cycleDuration_s;
|
|
rPeakDetectedInCycle = false;
|
|
}
|
|
|
|
double timeInCycle = cardiacCyclePosition_s / cycleDuration_s;
|
|
double baseVoltage = simulateEkgWaveform(timeInCycle);
|
|
for(const auto& leadName : leadNames) {
|
|
double leadModifier = 1.0 - (std::distance(leadNames.begin(), std::find(leadNames.begin(), leadNames.end(), leadName)) * 0.1);
|
|
ekgData[leadName].push_front(baseVoltage * leadModifier);
|
|
if (ekgData[leadName].size() > ekgHistorySize) ekgData[leadName].pop_back();
|
|
}
|
|
|
|
// --- Mechanical Simulation Update ---
|
|
// Define pressures outside the heart
|
|
double venousPressure = 5.0; // CVP
|
|
double pulmonaryArteryPressure = 20.0;
|
|
double aorticPressure = getAorticPressure();
|
|
|
|
// Determine chamber states based on EKG-timed cycle
|
|
if (timeInCycle >= 0.0 && timeInCycle < 0.15) { // Atrial Systole
|
|
leftAtrium.state = ChamberState::SYSTOLE;
|
|
rightAtrium.state = ChamberState::SYSTOLE;
|
|
// End of ventricular filling, capture EDV
|
|
if(oldCyclePosition < 0.15 && timeInCycle >= 0.15) {
|
|
leftVentricle.endDiastolicVolume_mL = leftVentricle.volume_mL;
|
|
}
|
|
} else {
|
|
leftAtrium.state = ChamberState::DIASTOLE;
|
|
rightAtrium.state = ChamberState::DIASTOLE;
|
|
}
|
|
|
|
if (timeInCycle >= 0.20 && timeInCycle < 0.5) { // Ventricular Systole
|
|
leftVentricle.state = ChamberState::SYSTOLE;
|
|
rightVentricle.state = ChamberState::SYSTOLE;
|
|
// End of ventricular ejection, capture ESV and calculate EF
|
|
if(oldCyclePosition < 0.5 && timeInCycle >= 0.5) {
|
|
leftVentricle.endSystolicVolume_mL = leftVentricle.volume_mL;
|
|
if (leftVentricle.endDiastolicVolume_mL > 0) {
|
|
ejectionFraction = (leftVentricle.endDiastolicVolume_mL - leftVentricle.endSystolicVolume_mL) / leftVentricle.endDiastolicVolume_mL;
|
|
}
|
|
}
|
|
} else {
|
|
leftVentricle.state = ChamberState::DIASTOLE;
|
|
rightVentricle.state = ChamberState::DIASTOLE;
|
|
}
|
|
|
|
// Update chamber pressures based on state (very simplified model)
|
|
leftAtrium.pressure_mmHg = (leftAtrium.state == ChamberState::SYSTOLE) ? 10.0 : 5.0;
|
|
rightAtrium.pressure_mmHg = (rightAtrium.state == ChamberState::SYSTOLE) ? 7.0 : 2.0;
|
|
leftVentricle.pressure_mmHg = (leftVentricle.state == ChamberState::SYSTOLE) ? 125.0 * sin((timeInCycle - 0.2) / 0.3 * M_PI) : 5.0;
|
|
rightVentricle.pressure_mmHg = (rightVentricle.state == ChamberState::SYSTOLE) ? 25.0 * sin((timeInCycle - 0.2) / 0.3 * M_PI) : 2.0;
|
|
|
|
// Update Valve Status
|
|
tricuspidValve.status = (rightAtrium.pressure_mmHg > rightVentricle.pressure_mmHg) ? ValveStatus::OPEN : ValveStatus::CLOSED;
|
|
mitralValve.status = (leftAtrium.pressure_mmHg > leftVentricle.pressure_mmHg) ? ValveStatus::OPEN : ValveStatus::CLOSED;
|
|
pulmonaryValve.status = (rightVentricle.pressure_mmHg > pulmonaryArteryPressure) ? ValveStatus::OPEN : ValveStatus::CLOSED;
|
|
aorticValve.status = (leftVentricle.pressure_mmHg > aorticPressure) ? ValveStatus::OPEN : ValveStatus::CLOSED;
|
|
|
|
// Update Chamber Volumes (simplified blood flow)
|
|
double flowRate = 500.0 * deltaTime_s; // mL/s
|
|
if (mitralValve.status == ValveStatus::OPEN) leftVentricle.volume_mL += flowRate;
|
|
if (tricuspidValve.status == ValveStatus::OPEN) rightVentricle.volume_mL += flowRate;
|
|
if (aorticValve.status == ValveStatus::OPEN) leftVentricle.volume_mL -= flowRate * 1.5;
|
|
if (pulmonaryValve.status == ValveStatus::OPEN) rightVentricle.volume_mL -= flowRate * 1.5;
|
|
|
|
// Clamp volumes to realistic values
|
|
leftVentricle.volume_mL = std::max(40.0, std::min(leftVentricle.volume_mL, 130.0));
|
|
rightVentricle.volume_mL = std::max(40.0, std::min(rightVentricle.volume_mL, 130.0));
|
|
}
|
|
|
|
double Heart::simulateEkgWaveform(double timeInCycle) {
|
|
double p_time = 0.1, q_time = 0.2, r_time = 0.22, s_time = 0.24, t_time = 0.4;
|
|
double p_amp = 0.15, q_amp = -0.1, r_amp = 1.0, s_amp = -0.25, t_amp = 0.3;
|
|
double p_sigma = 0.04, qrs_sigma = 0.02, t_sigma = 0.06;
|
|
double voltage = 0.0;
|
|
voltage += p_amp * gaussian(timeInCycle, p_time, p_sigma);
|
|
voltage += q_amp * gaussian(timeInCycle, q_time, qrs_sigma);
|
|
voltage += r_amp * gaussian(timeInCycle, r_time, qrs_sigma);
|
|
voltage += s_amp * gaussian(timeInCycle, s_time, qrs_sigma);
|
|
voltage += t_amp * gaussian(timeInCycle, t_time, t_sigma);
|
|
return voltage;
|
|
}
|
|
|
|
std::string Heart::getSummary() const {
|
|
std::stringstream ss;
|
|
ss.precision(2);
|
|
ss << std::fixed;
|
|
ss << "--- Heart Summary ---\n"
|
|
<< "Heart Rate (Measured): " << getHeartRate() << " bpm\n"
|
|
<< "Ejection Fraction: " << getEjectionFraction() * 100.0 << "%\n"
|
|
<< "Aortic Pressure: " << getAorticPressure() << " mmHg\n\n"
|
|
<< "--- Chambers ---\n"
|
|
<< " LV Volume: " << leftVentricle.volume_mL << " mL\n"
|
|
<< " LV Pressure: " << leftVentricle.pressure_mmHg << " mmHg\n"
|
|
<< " RV Volume: " << rightVentricle.volume_mL << " mL\n"
|
|
<< " RV Pressure: " << rightVentricle.pressure_mmHg << " mmHg\n\n"
|
|
<< "--- Valves ---\n"
|
|
<< " Aortic Valve: " << (aorticValve.status == ValveStatus::OPEN ? "OPEN" : "CLOSED") << "\n"
|
|
<< " Mitral Valve: " << (mitralValve.status == ValveStatus::OPEN ? "OPEN" : "CLOSED") << "\n";
|
|
return ss.str();
|
|
}
|
|
|
|
double Heart::getHeartRate() const { return measuredHeartRate; }
|
|
const std::map<std::string, std::deque<double>>& Heart::getEkgData() const { return ekgData; }
|
|
double Heart::getEjectionFraction() const { return ejectionFraction; }
|
|
double Heart::getAorticPressure() const {
|
|
// Aortic pressure decays over time, but gets "pumped up" by ventricular ejection
|
|
// This is a placeholder for a more complex arterial model.
|
|
if(aorticValve.status == ValveStatus::OPEN) {
|
|
return leftVentricle.pressure_mmHg;
|
|
}
|
|
// Simplified diastolic pressure decay
|
|
return 80.0 + 40.0 * exp(-cardiacCyclePosition_s);
|
|
}
|