/* 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 #include #include #include #include // 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 \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=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=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.Vm0) { // 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.ref0 && avj.refMAX_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 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= 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; }