Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/d.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: D

on:
push:
branches: [ "main" ]
branches: [ "**" ] # Run on all branches
pull_request:
branches: [ "main" ]

Expand Down
558 changes: 442 additions & 116 deletions README.md

Large diffs are not rendered by default.

18 changes: 15 additions & 3 deletions dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,19 @@
"dontelightfoot"
],
"copyright": "Copyright © 2025, dontelightfoot",
"description": "A minimal D application.",
"license": "proprietary",
"name": "apl"
"description": "Multi-Heart-Model: Heart-Brain Coupling and Organ-On-Chip Platform",
"license": "MIT",
"name": "primal_overlay",
"targetType": "executable",
"targetName": "primal_overlay",
"mainSourceFile": "source/app.d",
"sourcePaths": ["source"],
"importPaths": ["source"],
"dependencies": {},
"configurations": [
{
"name": "application",
"targetType": "executable"
}
]
}
274 changes: 274 additions & 0 deletions notebooks/03_baroreflex_sensitivity_testing.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Baroreflex Sensitivity Testing\n",
"\n",
"**Clinical Application:** Assessment of autonomic function and cardiovascular regulation\n",
"\n",
"**Learning Objectives:**\n",
"1. Understand baroreflex physiology and clinical significance\n",
"2. Simulate baroreceptor firing dynamics\n",
"3. Compute baroreflex sensitivity (BRS)\n",
"4. Interpret results in clinical contexts\n",
"\n",
"**Clinical Relevance:**\n",
"- Post-MI risk stratification (La Rovere et al. 1998)\n",
"- Heart failure prognosis\n",
"- Autonomic neuropathy assessment\n",
"- Syncope evaluation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"sys.path.append('..')\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from src.autonomic.baroreflex import Baroreceptor, BaroreflexController, compute_baroreflex_sensitivity\n",
"from src.validation.benchmarks import PhysiologicalBenchmarks\n",
"\n",
"%matplotlib inline\n",
"plt.rcParams['figure.figsize'] = (14, 10)\n",
"print(\"✓ Imports successful\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Part 1: Baroreceptor Firing Dynamics\n",
"\n",
"### Physiological Background\n",
"\n",
"Baroreceptors in carotid sinus and aortic arch sense arterial pressure changes:\n",
"- **High pressure** → Increased firing → ↑ Vagal, ↓ Sympathetic → ↓ HR, ↓ BP\n",
"- **Low pressure** → Decreased firing → ↓ Vagal, ↑ Sympathetic → ↑ HR, ↑ BP"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create baroreceptor model\n",
"baroreceptor = Baroreceptor()\n",
"\n",
"# Test across pressure range\n",
"pressures = np.linspace(60, 180, 100)\n",
"firing_rates = [baroreceptor.compute_firing_rate(p, 0.001) for p in pressures]\n",
"\n",
"# Plot pressure-firing relationship\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\n",
"\n",
"ax1.plot(pressures, firing_rates, 'b-', linewidth=2.5)\n",
"ax1.axvline(93, color='g', linestyle='--', label='Normal MAP', alpha=0.7)\n",
"ax1.axvline(100, color='r', linestyle='--', label='Sigmoid midpoint', alpha=0.7)\n",
"ax1.set_xlabel('Mean Arterial Pressure (mmHg)', fontsize=12, fontweight='bold')\n",
"ax1.set_ylabel('Firing Rate (spikes/s)', fontsize=12, fontweight='bold')\n",
"ax1.set_title('Baroreceptor Pressure-Firing Relationship\\n(Chapleau & Abboud 2001)', fontsize=13, fontweight='bold')\n",
"ax1.legend(fontsize=11)\n",
"ax1.grid(True, alpha=0.3)\n",
"\n",
"# Derivative (sensitivity)\n",
"dp = np.diff(pressures)\n",
"dfr = np.diff(firing_rates)\n",
"sensitivity = dfr / dp\n",
"ax2.plot(pressures[:-1], sensitivity, 'r-', linewidth=2)\n",
"ax2.set_xlabel('Pressure (mmHg)', fontsize=12, fontweight='bold')\n",
"ax2.set_ylabel('Sensitivity (spikes/s/mmHg)', fontsize=12, fontweight='bold')\n",
"ax2.set_title('Baroreceptor Sensitivity', fontsize=13, fontweight='bold')\n",
"ax2.grid(True, alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(f\"Firing at 80 mmHg: {baroreceptor.compute_firing_rate(80, 0.001):.1f} spikes/s\")\n",
"print(f\"Firing at 100 mmHg: {baroreceptor.compute_firing_rate(100, 0.001):.1f} spikes/s\")\n",
"print(f\"Firing at 120 mmHg: {baroreceptor.compute_firing_rate(120, 0.001):.1f} spikes/s\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Part 2: Baroreflex Control Loop\n",
"\n",
"Simulate complete baroreflex arc: Pressure → Baroreceptor → NTS → Autonomic output"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"controller = BaroreflexController()\n",
"\n",
"# Simulate pressure ramp\n",
"times = np.arange(0, 20, 0.01)\n",
"pressures = 93 + 30 * np.sin(2 * np.pi * times / 10)\n",
"\n",
"vagal_outputs = []\n",
"sympathetic_outputs = []\n",
"heart_rates = []\n",
"\n",
"for t, p in zip(times, pressures):\n",
" v, s = controller.compute_autonomic_output(p, 0.01, t)\n",
" hr = controller.compute_heart_rate_response(p, baseline_hr=105, dt=0.01, t=t)\n",
" vagal_outputs.append(v)\n",
" sympathetic_outputs.append(s)\n",
" heart_rates.append(hr)\n",
"\n",
"fig, axes = plt.subplots(4, 1, figsize=(14, 12), sharex=True)\n",
"\n",
"axes[0].plot(times, pressures, 'b-', linewidth=2)\n",
"axes[0].set_ylabel('Pressure\\n(mmHg)', fontsize=11, fontweight='bold')\n",
"axes[0].set_title('Baroreflex Response to Pressure Changes', fontsize=13, fontweight='bold')\n",
"axes[0].grid(True, alpha=0.3)\n",
"\n",
"axes[1].plot(times, vagal_outputs, 'g-', linewidth=2, label='Vagal')\n",
"axes[1].plot(times, sympathetic_outputs, 'r-', linewidth=2, label='Sympathetic')\n",
"axes[1].set_ylabel('Autonomic\\nOutput', fontsize=11, fontweight='bold')\n",
"axes[1].legend(fontsize=10)\n",
"axes[1].grid(True, alpha=0.3)\n",
"\n",
"axes[2].plot(times, heart_rates, 'purple', linewidth=2)\n",
"axes[2].set_ylabel('Heart Rate\\n(bpm)', fontsize=11, fontweight='bold')\n",
"axes[2].grid(True, alpha=0.3)\n",
"\n",
"# Phase relationship\n",
"axes[3].scatter(pressures, heart_rates, c=times, cmap='viridis', s=10, alpha=0.6)\n",
"axes[3].set_xlabel('Pressure (mmHg)', fontsize=11, fontweight='bold')\n",
"axes[3].set_ylabel('Heart Rate (bpm)', fontsize=11, fontweight='bold')\n",
"axes[3].set_title('Pressure-HR Relationship', fontsize=12, fontweight='bold')\n",
"axes[3].grid(True, alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(\"✓ Baroreflex demonstrates reciprocal autonomic control\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Part 3: Clinical Baroreflex Sensitivity Testing\n",
"\n",
"### Sequence Method (La Rovere et al. 1998)\n",
"\n",
"Identify sequences where systolic BP and RR interval change in same direction"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Simulate spontaneous BP variations\n",
"np.random.seed(42)\n",
"n_beats = 100\n",
"baseline_sbp = 120\n",
"baseline_rr = 850 # ms\n",
"\n",
"sbp_variations = baseline_sbp + np.random.randn(n_beats) * 5\n",
"rr_variations = baseline_rr + (sbp_variations - baseline_sbp) * 10 + np.random.randn(n_beats) * 5\n",
"\n",
"# Compute BRS\n",
"brs_values = []\n",
"for i in range(len(sbp_variations) - 3):\n",
" dp = sbp_variations[i+3] - sbp_variations[i]\n",
" drr = rr_variations[i+3] - rr_variations[i]\n",
" if abs(dp) > 1:\n",
" brs = drr / dp\n",
" if 3 < brs < 30:\n",
" brs_values.append(brs)\n",
"\n",
"mean_brs = np.mean(brs_values) if brs_values else 0\n",
"\n",
"fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n",
"\n",
"# Time series\n",
"axes[0,0].plot(sbp_variations, 'b-', linewidth=1.5)\n",
"axes[0,0].set_ylabel('SBP (mmHg)', fontsize=11, fontweight='bold')\n",
"axes[0,0].set_title('Systolic Blood Pressure', fontsize=12, fontweight='bold')\n",
"axes[0,0].grid(True, alpha=0.3)\n",
"\n",
"axes[0,1].plot(rr_variations, 'r-', linewidth=1.5)\n",
"axes[0,1].set_ylabel('RR Interval (ms)', fontsize=11, fontweight='bold')\n",
"axes[0,1].set_title('RR Intervals', fontsize=12, fontweight='bold')\n",
"axes[0,1].grid(True, alpha=0.3)\n",
"\n",
"# Scatter plot\n",
"axes[1,0].scatter(sbp_variations, rr_variations, alpha=0.6, s=50)\n",
"z = np.polyfit(sbp_variations, rr_variations, 1)\n",
"p = np.poly1d(z)\n",
"axes[1,0].plot(sbp_variations, p(sbp_variations), 'r--', linewidth=2, label=f'BRS = {z[0]:.1f} ms/mmHg')\n",
"axes[1,0].set_xlabel('SBP (mmHg)', fontsize=11, fontweight='bold')\n",
"axes[1,0].set_ylabel('RR Interval (ms)', fontsize=11, fontweight='bold')\n",
"axes[1,0].set_title('Baroreflex Sensitivity', fontsize=12, fontweight='bold')\n",
"axes[1,0].legend(fontsize=11)\n",
"axes[1,0].grid(True, alpha=0.3)\n",
"\n",
"# BRS distribution\n",
"axes[1,1].hist(brs_values, bins=20, edgecolor='black', alpha=0.7)\n",
"axes[1,1].axvline(mean_brs, color='r', linestyle='--', linewidth=2, label=f'Mean = {mean_brs:.1f}')\n",
"axes[1,1].axvspan(3, 30, alpha=0.2, color='green', label='Normal range')\n",
"axes[1,1].set_xlabel('BRS (ms/mmHg)', fontsize=11, fontweight='bold')\n",
"axes[1,1].set_ylabel('Frequency', fontsize=11, fontweight='bold')\n",
"axes[1,1].set_title('BRS Distribution', fontsize=12, fontweight='bold')\n",
"axes[1,1].legend(fontsize=10)\n",
"axes[1,1].grid(True, alpha=0.3, axis='y')\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"benchmarks = PhysiologicalBenchmarks()\n",
"print(f\"\\nBaroreflex Sensitivity: {mean_brs:.1f} ms/mmHg\")\n",
"print(f\"Normal range: {benchmarks.baroreflex.brs_normal.min_value:.1f}-{benchmarks.baroreflex.brs_normal.max_value:.1f} ms/mmHg\")\n",
"print(f\"Interpretation: {'Normal' if 3 < mean_brs < 30 else 'Impaired'}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"### Clinical Interpretation\n",
"\n",
"**BRS Values:**\n",
"- **>12 ms/mmHg**: Normal baroreflex function\n",
"- **6-12 ms/mmHg**: Moderately impaired\n",
"- **<6 ms/mmHg**: Severely impaired (high risk)\n",
"\n",
"**Clinical Applications:**\n",
"1. Post-MI risk stratification\n",
"2. Heart failure prognosis \n",
"3. Autonomic neuropathy detection\n",
"4. Drug effect assessment\n",
"\n",
"---\n",
"© 2025 Multi-Heart-Model Project | MIT License"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading
Loading