AFVP - A Realistic Ventricular Rhythm Model During AF 1.0.0
(35,743 bytes)
/* afvp.c */
/* Copyright (c)2006 by Jie Lian, All Rights Reserved */
/* This program simulates the interaction between AF and Vp. */
/* See Lian J., Mussig D., Lang V. Computer Modeling of Ventricular */
/* Rhythm During Atrial Fibrillation and Ventricular Pacing. */
/* IEEE Transactions on Biomedical Engineering 53(8): 1512-1520, 2006. */
/* Contact J. Lian jie.lian AT biotronik DOT com) */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 2 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program; if not, write to the Free Software Foundation */
/* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/* */
/* afvp.c and its dependents are freely available from Physionet - */
/* http://www.physionet.org/ - please report any bugs to the author above. */
/* Last modified: Jul 12th 2006 by Gari Clifford: */
/* - Adapted random number calls to make it ANSI C compliant */
/* - Added random number seeding and extra command line arguments */
/* - Removed BI (standby ventricular pacing basic interval) command line */
/* option */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h> // For random()
#include "afvp.h" // header file for the AF-VP model
#include "conio.h" // sometimes a missing system file
// definition of global model variables
EnvStruct env; // simulation environment
AtrStruct atr; // atrium model
AvjStruct avj; // AVJ model
VtrStruct vtr; // ventricle model
VeleStruct vele; // ventricular electrode model
unsigned int rseed; // seed for random number generator
// -can be passed as an argument
int main(int argc, char *argv[])
{
time_t utc;
if (argc<2){
fprintf(stdout,"Usage: %s <config_file> <random number seed>\n",argv[0]);
fprintf(stdout,"\n config file example: http://www.physionet.org/physiotools/afvp/config.txt \n");
// fprintf(stdout," BI = standby ventricular pacing basic interval in ms (default = 800 ms) \n");
fprintf(stdout," rseed = seed for random number generator - specify a long int for repeatability \n\n");
exit(1);
}
ReadConfig(argv[1]); // read model configuration file
//if (argc==3) vele.BI = atoi(argv[2])/1000.0; // overwrite BI
if (argc>2){
rseed = atol(argv[2]); // random seed
}
else {
/* use the system clock to give it a random number */
time(&utc);
rseed = (long int) utc;
}
if (argc>3) fprintf(stderr,"Ignoring input after 2nd argument\n");
ModelInit(); // initialize model parameters
// Main loop, keep running until MAX_RR beats or MAX_TIME timeout
while ((env.beat<env.MAX_RR) && (env.t<env.MAX_TIME)) {
UpdateAtTs(); // model update at sampling time
if (vtr.tmA[0]/vtr.AntDly+vtr.tmR[0]/vtr.RetDly>=1.0)
VtrFusion(); // detection of ventricular fusion
if (atr.t>=atr.NextAA)
AnteHitAvj(); // antegrade AF impluse hits AVJ
if (vtr.nR>0 && vtr.tmR[0]>=vtr.RetDly)
RetrHitAvj(); // VP-induced retrograde impulse hits AVJ
if (vtr.nA>0 && vtr.tmA[0]>=vtr.AntDly)
VtrSense(); // there is an ante conducted Vs
if (vele.t>=vele.NextVV)
VtrPace(); // timeout for a scheduled Vp
if (avj.tmA[0][0]>=avj.tmA[0][1])
AnteEscAvj(); // ante AV delay timeout and invade vtr
if (avj.tmR[0][0]>=avj.tmR[0][1])
RetrEscAvj(); // retr AV delay timeout and invade atr
if (avj.phase==PHASE4) { // check if AVJ depol & starts ref
if (avj.Vm>=avj.Vt) {
ActivateAvj();
StartAvjRef();
}
}
else { // check if AVJ ref ends & return to phase 4
if (avj.t>=avj.ref)
StartAvjPh4();
}
}
ModelExit();
} /*** end main ***/
/*********************************************************************
ErrMsg: display error message and abort simulation
Input: msg - message to be displayed
errcode - exit error code
*********************************************************************/
void ErrMsg(char *msg, int errcode)
{
printf("%s\n",msg);
exit(errcode);
}
/*********************************************************************
LogEvent: log event time and type
Input: fp - log file pointer
msg - message to be logged
*********************************************************************/
void LogEvent(FILE *fp, char *msg)
{
fprintf(fp,"T=%.3lf '%s ",env.t,msg);
if (avj.phase==PHASE4) fprintf(fp,"@PH4' ");
else fprintf(fp,"@REF' ");
}
/*********************************************************************
LogStat: log statistics after simulation
Input: fp - log file pointer
*********************************************************************/
void LogStat(FILE *fp)
{
long sum;
fprintf(fp,"\n----------------------------------------------------\n");
fprintf(fp,"Random seed = %d\n",rseed);
fprintf(fp,"Total time (s): %.3lf\n", env.t);
fprintf(fp,"Total beat: %ld\n", env.beat);
fprintf(fp,"Vs Count = %ld [%.2lf%%]\n",
vele.cnt[SENSE],100.0*vele.cnt[SENSE]/env.beat);
fprintf(fp,"Vp Count = %ld [%.2lf%%]\n",
vele.cnt[PACE], 100.0*vele.cnt[PACE]/env.beat);
sum = atr.nAf[REFRACTORY]+atr.nAf[PHASE4];
fprintf(fp,"Af Count = %ld(ref)+%ld(ph4) = %ld\n",
atr.nAf[REFRACTORY], atr.nAf[PHASE4], sum);
fprintf(fp,"Mean RR interval = %.3lf <%dppm>\n",
env.t/env.beat, (int)(env.beat*60/env.t));
fprintf(fp,"Mean PP interval = %.3lf <%dppm>\n",
env.t/sum, (int)(sum*60/env.t));
sum = vtr.nR+avj.nR;
fprintf(fp,"Active retr waves = %ld [%.2lf%%]\n",
sum, 100.0*sum/env.beat);
sum = vtr.nF[REFRACTORY]+vtr.nF[PHASE4];
fprintf(fp,"Vtr fusions = %ld(ref)+%ld(ph4) = %ld [%.2lf%%]\n",
vtr.nF[REFRACTORY], vtr.nF[PHASE4], sum, 100.0*sum/env.beat);
sum = avj.nF[REFRACTORY]+avj.nF[PHASE4];
fprintf(fp,"AVJ fusions = %ld(ref)+%ld(ph4) = %ld [%.2lf%%]\n",
avj.nF[REFRACTORY], avj.nF[PHASE4], sum, 100.0*sum/env.beat);
sum = atr.nF[REFRACTORY]+atr.nF[PHASE4];
fprintf(fp,"Atr fusions = %ld(ref)+%ld(ph4) = %ld [%.2lf%%]\n",
atr.nF[REFRACTORY], atr.nF[PHASE4], sum, 100.0*sum/env.beat);
sum = avj.nB[RETROGRADE]+vtr.nB[RETROGRADE];
fprintf(fp,"Retrgrade Block = %ld(avj)+%ld(vtr) = %ld [%.2lf%%]\n",
avj.nB[RETROGRADE], vtr.nB[RETROGRADE], sum, 100.0*sum/env.beat);
}
/*********************************************************************
ReadConfig: read configuration file
Input: fn - config filename
*********************************************************************/
void ReadConfig(char *fn)
{
double d;
FILE *fp;
char str[BUFLIM];
if ((fp=fopen(fn,"rt"))==NULL) ErrMsg("Cannot open config file",1);
// read parameters of the simulation environment
ReadLine(fp,-1,env.fnRR);
ReadLine(fp,-1,env.fnAA);
ReadLine(fp,-1,env.fnAV);
ReadLine(fp,-1,env.fnLOG);
env.MAX_RR = (long)ReadLine(fp,500,str);
env.MAX_TIME = ReadLine(fp,1000,str);
env.Ts = ReadLine(fp,0.001,str);
env.RR0 = ReadLine(fp,1.0,str);
// read parameters of the atrium model
atr.AA_MODEL = (int)ReadLine(fp,0,str);
atr.lambda = ReadLine(fp,10,str);
atr.AAstd = ReadLine(fp,0,str);
atr.dVmean = ReadLine(fp,33,str);
atr.dVstd = ReadLine(fp,0,str);
atr.AtrDly = ReadLine(fp,0.03,str);
atr.S1S2 = ReadLine(fp,0.4,str);
atr.S2S3 = ReadLine(fp,0.4,str);
// read parameters of the AVJ model
avj.Vt = ReadLine(fp,-40,str);
avj.Vr = ReadLine(fp,-90,str);
avj.dVdt = ReadLine(fp,30,str);
avj.MinAVDa = ReadLine(fp,0.07,str);
avj.MinAVDr = ReadLine(fp,0.07,str);
avj.alpha = ReadLine(fp,0.13,str);
avj.tau_c = ReadLine(fp,0.1,str);
avj.MinRef = ReadLine(fp,0.05,str);
avj.beta = ReadLine(fp,0.25,str);
avj.tau_r = ReadLine(fp,0.5,str);
avj.Ref_std = ReadLine(fp,0.0,str);
avj.delta = ReadLine(fp,10,str);
avj.theta = ReadLine(fp,10,str);
// read parameters of the ventricle model
vtr.AntDly = ReadLine(fp,0.05,str);
vtr.RetDly = ReadLine(fp,0.15,str);
vtr.ref = ReadLine(fp,0.1,str);
// read parameters of the RV electrode model
vele.VP_MODEL = (int)ReadLine(fp,0,str);
vele.BI = ReadLine(fp,1.0,str);
fclose(fp);
LogConfig(stdout); // display model parameters on terminal
}
/*********************************************************************
ReadLine: read a line from the config file
Input: fp - pointer to the config file
v - default value
str - char pointer (string read after '=' sign)
Output: 'val' read from the line if found, otherwise return 'v'
Note: Comment line starts with '%' sign
Value is separated from text by token '='
*********************************************************************/
double ReadLine(FILE *fp, double v, char *str)
{
char buf[BUFLIM], *p;
char tok[] = "="; // token followed by 'val'
double val; // value read from the line
*str = '\0'; // default: null string
do {
p = fgets(buf,sizeof(buf),fp); // read a line
if (p==NULL) return v; // force return on EOF
} while (strlen(buf)<=1 || buf[0]=='%'); // skip comment or blank lines
p = strstr(buf,tok); // search for "=" sign
if (p!=NULL) { // if found "="
p += strlen(tok); // skip "=" sign
sscanf(p,"%s",str); // read string after "=" sign
if (sscanf(p,"%lf",&val)>0) return val;// read value after "=" sign
} // otherwise
return v; // return default value
}
/*********************************************************************
LogConfig: log model configuration
Input: fp - output file pointer
*********************************************************************/
void LogConfig(FILE *fp)
{
fprintf(fp,"----------------------------------------------------\n");
fprintf(fp,"*** Simulation Environment ***\n");
fprintf(fp,"fnRR=%s\nfnAA=%s\nfnLog=%s\nfnAV=%s\n",
env.fnRR,env.fnAA,env.fnAV,env.fnLOG);
fprintf(fp,"MAX_RR=%ld\nMAX_TIME=%lf\nTs=%lf\nRR0=%lf\n",
env.MAX_RR,env.MAX_TIME,env.Ts,env.RR0);
fprintf(fp,"\n*** Atrium Model ***\n");
fprintf(fp,"AA_MODEL=%d\nlambda=%lf\nAAstd=%lf\n",
atr.AA_MODEL,atr.lambda,atr.AAstd);
fprintf(fp,"dVmean=%lf\ndVstd=%lf\nAtrDly=%lf\nS1S2=%lf\nS2S3=%lf\n",
atr.dVmean,atr.dVstd,atr.AtrDly,atr.S1S2,atr.S2S3);
fprintf(fp,"\n*** AVJ Model ***\n");
fprintf(fp,"Vt=%lf\nVr=%lf\ndVdt=%lf\nMinAVDa=%lf\nMinAVDr=%lf\n",
avj.Vt,avj.Vr,avj.dVdt,avj.MinAVDa,avj.MinAVDr);
fprintf(fp,"alpha=%lf\ntau_c=%lf\nMinRef=%lf\nbeta=%lf\n",
avj.alpha,avj.tau_c,avj.MinRef,avj.beta);
fprintf(fp,"tau_r=%lf\nRef_std=%lf\ndelta=%lf\ntheta=%lf\n",
avj.tau_r,avj.Ref_std,avj.delta,avj.theta);
fprintf(fp,"\n*** Ventricle Model ***\n");
fprintf(fp,"AntDly=%lf\nRetDly=%lf\nref=%lf\n",
vtr.AntDly,vtr.RetDly,vtr.ref);
fprintf(fp,"\n*** Ventricular Electrode Model ***\n");
fprintf(fp,"VP_MODEL=%d\nBI=%lf\n",vele.VP_MODEL,vele.BI);
fprintf(fp,"----------------------------------------------------\n\n");
}
/*********************************************************************
ModelInit: initialization of model parameters
*********************************************************************/
void ModelInit()
{
int i;
if ((env.fprr=fopen(env.fnRR,"wt"))==NULL) // output file of RR intervals
ErrMsg("Cannot open output file of RR intervals",1);
if ((env.fpaa=fopen(env.fnAA,"wt"))==NULL) // output file of AA intervals
ErrMsg("Cannot open output file of AA intervals",1);
if ((env.fpav=fopen(env.fnAV,"wt"))==NULL) // output file of AVJ status
ErrMsg("Cannot open output file of AVJ status",1);
if ((env.fplog=fopen(env.fnLOG,"wt"))==NULL)// output file of event log
ErrMsg("Cannot open output file of event log",1);
env.beat = 0; // clear beat counter
env.t = 0; // reset simulation time clock
avj.phase = PHASE4; // set AVJ phase = PHASE4
avj.Vm = avj.Vr; // set Vm to Vr
avj.V4 = avj.dVdt * env.Ts; // initialize V4 (mV/ms)
avj.RT0 = 0; // AVJ start recovery
avj.nA = avj.nR = 0; // no activation wave in AVJ
vtr.nA = vtr.nR = 0; // no activation wave in vtr
for (i=0;i<MAX_AVJ_TM;i++) { // disable AVJ activation timers
avj.tmA[i][0] = -1;
avj.tmR[i][0] = -1;
}
for (i=0;i<MAX_VENT_TM;i++) { // disable vtr activation timers
vtr.tmA[i] = -1;
vtr.tmR[i] = -1;
}
vele.cnt[PACE] = 0; // clear Vp counter
vele.cnt[SENSE] = 0; // clear Vs counter
atr.nAf[0] = atr.nAf[1] = 0; // clear AF event counters
atr.nF[0] = atr.nF[1] = 0; // clear atrial fusion counters
avj.nF[0] = avj.nF[1] = 0; // clear AVJ fusion counters
vtr.nF[0] = vtr.nF[1] = 0; // clear ventricle fusion counters
vtr.tmA0 = 0; // clear last ante AV timeout time
vtr.tmR0 = 0; // clear last Vp delivery time
// Get the first ventricular pacing interval
vele.NextVV = GetVpIntv(env.RR0,vele.BI,vele.BI,vele.VP_MODEL);
// randomize(); // Initialize randomization
srandom(rseed); // Initialize randomization with seeded random number
// Get the first random interval for next AF depol.
atr.NextAA = GetAfIntv(atr.lambda,atr.AAstd,atr.AA_MODEL);
fprintf(env.fpaa,"%lf\n",atr.NextAA);
LogConfig(env.fplog); // log config parameters
}
/*********************************************************************
ModelExit: exit simulation
*********************************************************************/
void ModelExit()
{
LogStat(stdout); // output statistics to stdout
LogStat(env.fplog); // output statistics to log file
fclose(env.fprr);
fclose(env.fpaa);
fclose(env.fpav);
fclose(env.fplog);
}
/*********************************************************************
UpdateAtTs: model update at sampling time
*********************************************************************/
void UpdateAtTs()
{
int i;
env.t += env.Ts; // update simulation timer
atr.t += env.Ts; // update atrial timer
vtr.t += env.Ts; // update ventricle timer
vele.t += env.Ts; // update Vp timer
// update AVJ and ventricle activation timers
for (i=0;i<avj.nA;i++) avj.tmA[i][0] += env.Ts;
for (i=0;i<avj.nR;i++) avj.tmR[i][0] += env.Ts;
for (i=0;i<vtr.nA;i++) vtr.tmA[i] += env.Ts;
for (i=0;i<vtr.nR;i++) vtr.tmR[i] += env.Ts;
if (avj.phase==PHASE4) { // during phase 4
avj.Vm += avj.V4; // Vm linear rise from Vr to Vt
if (avj.Vm>=avj.Vt) avj.dir = ANTEGRADE;
}
else // during refractory period
avj.t += env.Ts; // update AVJ refractory timer
}
/*********************************************************************
AnteHitAvj: event service when antegrade impulse hits AVJ
*********************************************************************/
void AnteHitAvj()
{
double ext;
long r;
LogEvent(env.fplog,"AnteHitAvj");
atr.t = 0; // reset atrium timer
atr.nAf[avj.phase]++; // increment AF impulse counter
// Get the random interval for next AF depolarization
atr.NextAA = GetAfIntv(atr.lambda,atr.AAstd,atr.AA_MODEL);
fprintf(env.fpaa,"%lf\n",atr.NextAA);
if (avj.phase==PHASE4) { // AVJ in phase IV
//srandom(-1000);
r = (long) random(); // simulate variation of dV
atr.dV = gaussian(atr.dVmean,atr.dVstd,ran2(&r),ran2(&r));
if (atr.dV>0) avj.Vm += atr.dV; // delta increment of Vm
if (avj.Vm>=avj.Vt) avj.dir = ANTEGRADE;
fprintf(env.fplog, // log after AnteHitAvj() service @PH4
"NextAA=%.3lf, Vm=%.2lf+%.2lf=%.2lf\n",
atr.NextAA, avj.Vm-atr.dV, atr.dV, avj.Vm);
}
else { // AVJ in refractory period
r = atr.dV/(avj.Vt-avj.Vr); // relative strength of the AF impulse
if (r>1) r = 1.0; // with upper limit 1.0
r = pow(avj.t/avj.ref,avj.theta)*pow(r,avj.delta);
ext = avj.MinRef*r;
avj.ref += ext; // ref extension due to concealed AF
fprintf(env.fplog, // log after AnteHitAvj() service @REF
"NextAA=%.3lf, ref=%.3lf+%.3lf=%.3lf\n",
atr.NextAA, avj.ref-ext, ext, avj.ref);
}
}
/*********************************************************************
RetrHitAvj: event service when retrograde impulse hits AVJ
*********************************************************************/
void RetrHitAvj()
{
int i;
double r,ext;
LogEvent(env.fplog,"RetrHitAvj");
StopVtrTm(RETROGRADE); // stop a retr vtr activation timer
if (avj.phase==PHASE4) { // retr invasion in phase 4
if (avj.Vm<avj.Vt) { // retr AVJ depolarization
avj.Vm = avj.Vt; // suprathreshold AVJ immediately
avj.dir = RETROGRADE; // retrograde activation
fprintf(env.fplog, // log after RetHitAvj() service @PH4
"vtr.nR=%d, Vm=%.2lf Retr AVJ activation\n", vtr.nR, avj.Vm);
}
else { // simultaneous ante/retr depol (rare), no wave in AVJ
avj.dir = BIDIRECTION;
fprintf(env.fplog, // log after RetHitAvj() service @PH4
"vtr.nR=%d, Vm=%.2lf Bidir AVJ activation\n", vtr.nR, avj.Vm);
}
}
else { // retr invasion in refractory period
if (avj.nA>0) { // presence of ante wave in AVJ
StopAvjTm(ANTEGRADE); // stop the ante AVJ timer
avj.nF[avj.phase]++; // incr AVJ fusion counter
}
avj.nB[RETROGRADE]++; // incr counter of blocked retr waves
r = pow(avj.t/avj.ref,avj.theta); // strong impulse
ext = avj.MinRef*r;
avj.ref += ext; // ref extension by concealed impulse
fprintf(env.fplog, // log after RetHitAvj() service @REF
"vtr.nR=%d, avj.nA=%d, nB=%d, nF=%d, ref=%.2lf+%.2lf=%.2lf\n",
vtr.nR,avj.nA,avj.nB[RETROGRADE],avj.nF[REFRACTORY],
avj.ref-ext,ext,avj.ref);
}
}
/*********************************************************************
ActivateAvj: AVJ depolarization service
*********************************************************************/
void ActivateAvj()
{
LogEvent(env.fplog,"ActivateAvj");
avj.RT = env.t-avj.RT0; // calculated AVJ recovery time
avj.AVDa = avj.MinAVDa+avj.alpha*exp(-avj.RT/avj.tau_c); // ante AV delay
avj.AVDr = avj.MinAVDr+avj.alpha*exp(-avj.RT/avj.tau_c); // retr AV delay
if (avj.dir==ANTEGRADE) { // antegrade AVJ activation
if (avj.nR==0) { // no existing retr activation in AVJ
StartAvjTm(ANTEGRADE); // start a new timer for ante AV delay
fprintf(env.fpav, // record AVJ status
"%.3lf %d %.3lf %.3lf ", env.t, avj.dir, avj.RT, avj.AVDa);
fprintf(env.fplog, // log after ActivateAvj() service
"start AVDa = %.3lf, avj.nA=%d\n", avj.AVDa, avj.nA);
}
else { // collide w/ existing retr wave
StopAvjTm(RETROGRADE); // stop a retr AVJ activation timer
avj.nF[avj.phase]++; // incr AVJ collision counter (Phase 4)
fprintf(env.fpav, // record AVJ status
"%.3lf %d %.3lf %d ", env.t, avj.dir, avj.RT, -1);
fprintf(env.fplog, // log after ActivateAvj() service
"collide w/ retr wave, avj.nF(%d)=%ld\n", avj.phase, avj.nF[avj.phase]);
}
}
else if (avj.dir==RETROGRADE) { // retrograde AVJ activation
if (avj.nA==0) { // no existing ante activation in AVJ
StartAvjTm(RETROGRADE); // start a new timer for retr AV delay
fprintf(env.fpav, // record AVJ status
"%.3lf %d %.3lf %.3lf ", env.t, avj.dir, avj.RT, avj.AVDr);
fprintf(env.fplog, // log after ActivateAvj() service
"start AVDr = %.3lf, avj.nR=%d\n", avj.AVDr, avj.nR);
}
else { // collide w/ existing ante wave in AVJ
StopAvjTm(ANTEGRADE); // stop an ante AVJ activation timer
avj.nF[avj.phase]++; // incr AVJ collision counter (phase 4)
fprintf(env.fpav, // record AVJ status
"%.3lf %d %.3lf %d ", env.t, avj.dir, avj.RT, -1);
fprintf(env.fplog, // log after ActivateAvj() service
"collide w/ ante wave, avj.nF(%d)=%ld\n", avj.phase, avj.nF[avj.phase]);
}
}
else { // simultaneous bidirectional depol
avj.nF[avj.phase]++; // incr AVJ collision counter (phase 4)
fprintf(env.fpav, // record AVJ status
"%.3lf %d %.3lf %d ", env.t, avj.dir, avj.RT, -1);
fprintf(env.fplog, // log after ActivateAvj() service
"bidir depolarization, avj.nF(%d)=%ld\n", avj.phase, avj.nF[avj.phase]);
}
}
/*********************************************************************
StartAvjRef: AVJ starts refractory period
*********************************************************************/
void StartAvjRef()
{
long r;
// calculate AVJ refractory period
avj.ref = avj.MinRef+avj.beta*(1-exp(-avj.RT/avj.tau_r));
// Assume AVJ refractory period does not exceed the AVD
if (avj.nA>0 && avj.ref<avj.AVDa) avj.ref = avj.AVDa; // limited by AVD
if (avj.nR>0 && avj.ref<avj.AVDr) avj.ref = avj.AVDr; // limited by AVD
//srandom(-1000);
r = (long) random();
avj.ref = gaussian(avj.ref,avj.Ref_std,ran2(&r),ran2(&r));
avj.phase = REFRACTORY; // update AVJ phase to REFRACTORY
avj.t = 0; // start AVJ refractory timer
fprintf(env.fpav,"%.3lf\n", avj.ref); // record AVJ status
fprintf(env.fplog, // log after StartAvjRef() service
"T=%.3lf 'Start AVJ REF' RT=%.3lf, ref=%.3lf\n",
env.t, avj.RT, avj.ref);
}
/*********************************************************************
StartAvjPh4: AVJ starts phase 4
*********************************************************************/
void StartAvjPh4()
{
avj.phase = PHASE4; // update AVJ phase to PHASE4
avj.Vm = avj.Vr; // set Vm to Vr
avj.RT0 = env.t; // AVJ starts recovery
fprintf(env.fplog, // log after StartAvjPh4() service
"T=%.3lf 'Start AVJ PH4' Vm=%.2lf, avj.nA=%d, avj.nR=%d\n",
env.t, avj.Vm, avj.nA, avj.nR);
}
/*********************************************************************
StartAvjTm: start a new activation timer in AVJ
Input: dir - direction of the activation
*********************************************************************/
void StartAvjTm(int dir)
{
if (dir==ANTEGRADE) { // a new timer for ante AV delay
avj.tmA[avj.nA][0] = 0; // start the timer
avj.tmA[avj.nA][1] = avj.AVDa; // set ante AV delay time
avj.nA++; // allow multiple ante waves in AVJ
}
else { // a new timer for retr AV delay
avj.tmR[avj.nR][0] = 0; // start the timer
avj.tmR[avj.nR][1] = avj.AVDr; // set retr AV delay time
avj.nR++; // allow multiple retr waves in AVJ
}
}
/*********************************************************************
StopAvjTm: stop an activation timer in AVJ
Input: dir - direction of the activation
*********************************************************************/
void StopAvjTm(int dir)
{
int i;
if (dir==ANTEGRADE) { // stop an ante AV delay timer
for (i=0;i<avj.nA-1;i++) { // update AVJ ante activation queue
avj.tmA[i][0] = avj.tmA[i+1][0]; // activation timer
avj.tmA[i][1] = avj.tmA[i+1][1]; // conduction delay
}
avj.nA--; // decr ante AVJ activation counter
avj.tmA[avj.nA][0] = -1; // disable an ante AVJ timer
}
else { // stop a retr AV delay timer
for (i=0;i<avj.nR-1;i++) { // update AVJ retr activation queue
avj.tmR[i][0] = avj.tmR[i+1][0]; // activation timer
avj.tmR[i][1] = avj.tmR[i+1][1]; // conduction delay
}
avj.nR--; // decr retro AVJ activation counter
avj.tmR[avj.nR][0] = -1; // disable a retr AVJ timer
}
}
/*********************************************************************
StartVtrTm: start a new activation timer in ventricle
Input: dir - direction of the activation
*********************************************************************/
void StartVtrTm(int dir)
{
if (dir==ANTEGRADE) { // a new timer for ante vtr delay
vtr.tmA[vtr.nA] = 0; // starts an ante activation in vtr
vtr.nA++; // incr ante vtr activation counter
if (vtr.nA>MAX_VENT_TM) ErrMsg("Too many ante waves in ventricle!",2);
}
else { // a new timer for retr vtr delay
vtr.tmR[vtr.nR] = 0; // starts a retr activation in vtr
vtr.nR++; // incr retr vtr activation counter
if (vtr.nR>MAX_VENT_TM) ErrMsg("Too many retr waves in ventricle!",2);
}
}
/*********************************************************************
StopVtrTm: stop an activation timer in ventricle
Input: dir - direction of the activation
*********************************************************************/
void StopVtrTm(int dir)
{
int i;
if (dir==ANTEGRADE) { // stop an ante vtr delay timer
for (i=0;i<vtr.nA-1;i++) // update ante vtr activation queue
vtr.tmA[i] = vtr.tmA[i+1];
vtr.nA--; // decr number of ante waves in vtr
vtr.tmA[vtr.nA] = -1; // stop an ante vtr activation timer
}
else { // stop a retr vtr delay timer
for (i=0;i<vtr.nR-1;i++) // update vtr retr activation queue
vtr.tmR[i] = vtr.tmR[i+1];
vtr.nR--; // decr retr vtr activation counter
vtr.tmR[vtr.nR] = -1; // stop a retr vtr activation timer
}
}
/*********************************************************************
AnteEscAvj: event service when ante wave escapes AVJ and invade vtr
*********************************************************************/
void AnteEscAvj()
{
LogEvent(env.fplog,"AnteEscAvj");
StopAvjTm(ANTEGRADE); // stop an ante AVJ activation timer
if (avj.nA==0) avj.RT0 = env.t; // AVJ starts recovery
if (env.t-vtr.tmA0<=vtr.ref) { // non-capture because vtr refr
vtr.nB[ANTEGRADE]++; // will not generate ante wave in vtr
fprintf(env.fplog, // log after AnteEscAvj() service
"avj.nA=%d, vtr.nB(a)=%d(V-Blk)\n", avj.nA, vtr.nB[ANTEGRADE]);
return; // force return
} // otherwise, capture vtr and ...
StartVtrTm(ANTEGRADE); // starts an ante activation in vtr
vtr.tmA0 = env.t; // update last ante captured time
fprintf(env.fplog, // log after AnteEscAvj() service
"avj.nA=%d, vtr.nA=%d\n", avj.nA, vtr.nA);
}
/*********************************************************************
RetrEscAvj: event service when retr wave escapes AVJ and invade atrium
*********************************************************************/
void RetrEscAvj()
{
double t;
LogEvent(env.fplog,"RetrEscAvj");
atr.nF[avj.phase]++; // incr atrial fusion counter
if (atr.NextAA-atr.t > atr.AtrDly) { // invade SA node before next impulse
t = GetAfIntv(atr.lambda,atr.AAstd,atr.AA_MODEL); // reset SA node
fprintf(env.fpaa,"%lf\n",t);
atr.NextAA += t; // update arrival time of next AF
} // otherwise, simply atrial fusion
StopAvjTm(RETROGRADE); // stop a retr AVJ activation timer
if (avj.nR==0) avj.RT0 = env.t; // AVJ starts recovery
fprintf(env.fplog, // log after RetrEscAvj() service
"avj.nR=%d, atr.nF(%d)=%ld, nextAA(%.3lf)+%.3lf=%.3lf\n",
avj.nR, avj.phase, atr.nF[avj.phase], atr.NextAA-t, t, atr.NextAA);
}
/*********************************************************************
VtrFusion: event service when detecting ventricular fusion
*********************************************************************/
void VtrFusion()
{
int i;
LogEvent(env.fplog,"VtrFusion");
vtr.nF[avj.phase]++; // incr ventricular fusion counter
StopVtrTm(ANTEGRADE); // stop an ante vtr activation timer
StopVtrTm(RETROGRADE); // stop a retr vtr activation timer
fprintf(env.fplog, // log after VtrFusion() service
"vtr.nF(%d)=%ld, vtr.nA=%d, vtr.nR=%d\n",
avj.phase, vtr.nF[avj.phase], vtr.nA, vtr.nR);
}
/*********************************************************************
RegEventV: register ventricular event (Vs/Vp) and update parameters
Input: event - PACE or SENSE
*********************************************************************/
void RegEventV(int event)
{
env.beat++; // increment beat counter
vele.VpFlg = event; // flag for sensed/paced beat
vele.cnt[event]++; // increment Vs/Vp counter
vele.RR = vtr.t; // record RR interval ended with Vs/Vp
// Get the next ventricular pacing interval
vele.NextVV = GetVpIntv(vele.RR,vele.BI,vele.NextVV,vele.VP_MODEL);
vele.t = 0; // reset Vp clock timer (inhibit Vp)
vtr.t = 0; // clear vtr time to measure RR interval
fprintf(env.fprr, // output RR interval and Vp/Vs flag
"%.3lf %d\n",vele.RR,event);
}
/*********************************************************************
VtrSense: event service for ventricular sense
*********************************************************************/
void VtrSense()
{
int i;
LogEvent(env.fplog,"VtrSense");
RegEventV(SENSE); // update parameters on Vs event
for (i=0;i<vtr.nA-1;i++) // update ante vtr activation timers
vtr.tmA[i] = vtr.tmA[i+1];
vtr.nA--;
vtr.tmA[vtr.nA] = -1; // stop an ante vtr activation timer
fprintf(env.fplog, // log after VtrSense() service
"vtr.nA=%d, nextVV=%.3lf, Vs#%ld, Beat#%ld, RR=%.3lf\n",
vtr.nA, vele.NextVV, vele.cnt[SENSE], env.beat, vele.RR);
}
/*********************************************************************
VtrPace: event service for ventricular pace
*********************************************************************/
void VtrPace()
{
LogEvent(env.fplog,"VtrPace");
RegEventV(PACE); // update parameters on Vp event
if (env.t-vtr.tmR0<=vtr.ref) { // non-capture because vtr refr
vtr.nB[RETROGRADE]++; // will not generate a retr wave in vtr
return; // force return
} // otherwise, Vp capture vtr and ...
StartVtrTm(RETROGRADE); // starts a retr activation in vtr
vtr.tmR0 = env.t; // update last captured Vp time
fprintf(env.fplog, // log after VtrPace() service
" vtr.nR=%d, nextVV=%.3lf, Vp#%ld, Beat#%ld, RR=%.3lf\n",
vtr.nR, vele.NextVV, vele.cnt[PACE], env.beat, vele.RR);
}
/*********************************************************************
GetAfIntv: get next Afib interval
*********************************************************************/
double GetAfIntv(double lambda, double sd, int method)
{
long init;
long k;
double next_afib_int, mean, next_afib_rate;
k = atr.nAf[0]+atr.nAf[1];
mean = 1/lambda;
//srandom(-1000);
init = (long) random();
switch (method) {
case 0: // random value from truncated exponential distribution
// next_afib_int = expdev(mean,&init);
next_afib_int = 0.05+expdev(mean-0.05,&init); // limit AA >= 50ms
break;
case 1: // inverse of random value from Poisson distribution
next_afib_rate = poidev(lambda,&init);
next_afib_rate++; // Ensure next_afib_rate>0 (avoid divided by 0)
next_afib_int = 1.0/next_afib_rate;
break;
case 2: // random value from uniform distribution
next_afib_int = mean+sd*mean*ran2(&init);
break;
case 3: // random value from Gaussian distribution
next_afib_int = gaussian(mean,sd*mean,ran2(&init),ran2(&init));
break;
case 4: // atrial pacing protocl with programmed S1S2
if (k < env.MAX_RR/2)
next_afib_int = 1.0/lambda;
else
next_afib_int = atr.S1S2;
break;
case 5: // atrial pacing protocl with programmed S1S2/S2S3
if (k < env.MAX_RR/2)
next_afib_int = 1.0/lambda;
else {
if (k%2==0) // even beat
next_afib_int = atr.S1S2;
else // odd beat
next_afib_int = atr.S2S3;
}
break;
default: // fixed rate, atrial flutter protocol
next_afib_int = 1.0/lambda;
break;
}
if (next_afib_int < 0.05) next_afib_int = 0.05; // ensure interval >= 50ms
return next_afib_int;
}