function [ cycle, div_period, div_mass ] = singlerun( model, plot_on ) % Params and times are global so that they can be changed in other routines % - esp useful for params, for parameter space examinations if nargin == 1 plot_on = false; end % ODE solver stuff options = odeset( 'Events', @model_events, 'Refine', 4, ... 'RelTol', 1e-005, 'AbsTol', 0.00001 ); % Start with licensed origins (order_condition==1) in an unbudded cell. order_condition = 1; budding_condition = false; % optional index set var.TIME = 1; var.MASS = 2; var.CLN2 = 3; var.CLB2 = 4; var.CLB5 = 5; var.SIC1 = 6; var.CDC6 = 7; var.C2 = 8; var.C5 = 9; var.F2 = 10; var.F5 = 11; var.SIC1P = 12; var.C2P = 13; var.C5P = 14; var.CDC6P = 15; var.F2P = 16; var.F5P = 17; var.SWI5T = 18; var.SWI5 = 19; var.IEP = 20; var.CDC20T = 21; var.CDC20 = 22; var.CDH1T = 23; var.CDH1 = 24; var.CDC14T = 25; var.CDC14 = 26; var.NET1T = 27; var.NET1 = 28; var.RENT = 29; var.TEM1 = 30; var.CDC15 = 31; var.PPX = 32; var.PDS1 = 33; var.ESP1 = 34; var.ORI = 35; var.BUD = 36; var.SPN = 37; var.VI20 = 38; var.LTE = 39; var.BUB2 = 40; % Parameter name assignments, of form 'name = params(x,y)' ki20_c = model.params( 11, 6 ); ki20_r = model.params( 11, 7 ); Lte1l = model.params( 23, 1 ); Lte1h = model.params( 23, 2 ); Bub2l = model.params( 23, 3 ); Bub2h = model.params( 23, 4 ); mdt = model.params( 26, 2 ); MorD = model.params( 26, 3 ); KEZ2 = model.params( 26, 4 ); % Calculate mass proportion to daughter as per Kathy Chen kg = log( 2 ) / mdt; D = 1.026 / kg - 32; f = exp( - kg * D ); % Set up for integration pheno = []; tstart = model.times( 1 ); stop_int = 0; div_n = 0; cycle = 0; div_history = []; div_mass = 0.0; div_period = 0.0; % stop_int is a flag that stays 0 until the integration hits a terminal % event: either successful achievement of a recurrent solution that meets % the rules for the cell cycle, or failure of the model (see below). while ( stop_int == 0 ) sol = ode23s( @model_ode, [ tstart, tstart + model.times( 2 ) ], ... model.init, options, model.params, model.times ); % Note: Sol results come as an array with either the last event or the % last two sequential events stored in a row vector. Therefore, take the % size and only look at the last column. [ y_row, y_col ] = size( sol.ye ); [ x_row, x_col ] = size( sol.xe ); [ i_row, i_col ] = size( sol.ie ); if ( sol.ie( i_col ) == 100 ) % Problem with the ode23s integrator for the given set of parameters % etc. disp( 'Integration problem' ); stop_int = 1; else % Save 100 data points for the segment solved, store on top of % previous data in 'pheno'. time_span = linspace( tstart, sol.xe( x_col ), 30 ); y = deval( sol, time_span ); segment = [ time_span; y ]; pheno = [ pheno, segment ]; if plot_on == true hold off; figure( 1 ); plot( pheno( var.TIME, : ), pheno( var.MASS, : ), ... pheno( var.TIME, : ), pheno( var.CLB2, : ), ... pheno( var.TIME, : ), pheno( var.CLB5, : ), ... pheno( var.TIME, : ), 0.01 * pheno( var.PDS1, : ), ... pheno( var.TIME, : ), pheno( var.ESP1, : ), ... pheno( var.TIME, : ), pheno( var.CDC20, : ) ... ); axis( [ model.times( 1 ) sol.xe( x_col ) 0 5 ] ); drawnow; end % Reset model.initial conditions to the last data point, reset time to the % last time, in preparation for another round of integration, if % appropriate. model.init = sol.ye( :, y_col ); tstart = sol.xe( x_col ); % Decide whether to integrate another round based on the discrete event % that stopped the last round. switch ( sol.ie( i_col ) ) % Cycle is the value returned for this function. % The value is coded as follows: % 1: successful cycle % 1000: Integration problem % 2000: Ordering error % 3000: Division without budding % 4000: too big % 5000: Time expired % For ordering errors, too big, and unbudded cell at division, add 100 * % order_condition to save information % For all except successful cycle, add div_n to record number of % completed divisions at the time of the error case 100 % Integration problem - exit the while loop disp( sprintf( 'Stopped because of integration problem at time %.0d', ... floor( sol.xe( x_col ) ) ) ); stop_int = 1; cycle = 1000 + div_n; case 1 % If the integration hits the time limit, don't repeat the % integration - exit the while loop disp( sprintf( 'Stopped because time ran out at time %.0d', ... floor( sol.xe( x_col ) ) ) ); stop_int = 1; cycle = 5000 + div_n; case 2 % If the integration hits conditions for cell division, divide the % cell (a preset fraction of the mass goes to daughter, % replicated DNA and spindle are removed), % and test to see if a recurrent solution has been found by % comparing variable values at division to those at previous % divisions. If a recurrent solution has not been found, % do another round of integration. % NOTE that division is allowed only if the order_condition % indicates that since the last division, ORI was 0, then 1, % then SPN was 1, then Esp1 went through 0.1. % Also the cell must be budded Otherwise the model fails and % the loop is exited. % order_condition rules: % Cell division requires replicated DNA assembled on a % spindle, with subsequent Esp1 activation. % An early cell-cycle event, before model.initiation of % replication, spindle assembly and Esp1 action, must be % origin re-licensing. % Therefore, % State 0 is unlicensed origins in a newborn cell; % State 1 is licensed origins (given previous state 0). % State 2 is replicated DNA (ORI>1, given previous State 1); % State 3 is assembled spindle (SPN>1, given previous State 2) and % State 4 is activated Esp1 leading to chromosome separation % (Esp1 >0.1 given previous State 3). % Assume that only the exact sequence of states 0,1,2,3,4 is % compatible with ultimate division, and that division is % triggered when Clb2 drops through a threshold. % This drop is therefore considered LETHAL and an % unconditional model failure in any state but state 4. % This is what order_condition follows. if order_condition == 4 && budding_condition % if order_condition >= 3 && budding_condition if plot_on == true disp( sprintf( [ 'Cell divides, Lte1 low, SPN=0, BUD=0 at ' ... 'time %.0d' ], ... floor( sol.xe( x_col ) ) ) ); end div_mass = model.init( 1 ); model.init( 1 ) = abs( MorD + f - 1 ) * model.init( 1 ); model.init( 38 ) = Lte1l; model.init( 35 ) = 0; model.init( 36 ) = 0; budding_condition = false; % If the drop in Clb2 also lowers Clb2+Clb5 below % the threshold % for origin reloading, then ORI becomes 0 and the Flag is % changed to reflect this. if model.init( 4 ) + model.init( 3 ) < KEZ2 model.init( 34 ) = 0; order_condition = 1; if plot_on == true disp( sprintf( 'Origins reload at time %.0d', ... floor( sol.xe( x_col ) ) ) ); end else order_condition = 0; end % Store terminal results in division history this_div = [ tstart, model.init' ]; div_history = [ div_history; this_div ]; div_n = div_n + 1; if plot_on == true disp( sprintf( '%d divisions at time %.0d', ... div_n, floor( sol.xe( x_col ) ) ) ); end % Check to see if a steady state has been achieved; if so, % discontinue the integration and return a cycle value of 1. % Note that periods up to 4 for the recurrence are tested for. % comp_divs returns the root mean square deviation comparing % terminal values for two divisions. % tolerance = 0; tolerance = 1e-3; rows = size( div_history, 1 ); if ( div_n > 1 ) test = comp_divs( div_history, 1 ); if ( test < tolerance ) disp( sprintf( 'Steady state achieved after %d divisions', ... div_n ) ); cycle = 1; stop_int = 1; div_period = div_history( rows, 1 ) - div_history( rows - 1, 1 ); end end if ( div_n > 2 ) && ( cycle == 0 ) test = comp_divs( div_history, 2 ); if ( test < tolerance ) disp( sprintf( [ 'Period 2 steady state achieved after ' ... '%d divisions' ], ... div_n ) ); cycle = 1; stop_int = 1; div_period = div_history( rows, 1 ) - div_history( rows - 2, 1 ); end end if ( div_n > 3 ) && ( cycle == 0 ) test = comp_divs( div_history, 3 ); if ( test < tolerance ) disp( sprintf( [ 'Period 3 steady state achieved after ' ... '%d divisions' ], ... div_n ) ); cycle = 1; stop_int = 1; div_period = div_history( rows, 1 ) - div_history( rows-3, 1 ); end end if ( div_n > 4 ) && ( cycle == 0 ) test = comp_divs( div_history, 4 ); if ( test < tolerance ) disp( sprintf( [ 'Period 4 steady state achieved after ' ... '%d divisions' ], ... div_n ) ); cycle = 1; stop_int = 1; div_period = div_history( rows, 1 ) - div_history( rows - 4, 1 ); end end if ( div_n > 1 ) && ( cycle == 0 ) if plot_on == true disp( sprintf( 'Steady state not achieved in %d divisions ', ... div_n ) ); end end elseif order_condition ~= 4 && budding_condition % If the order condition is violated, then the model fails. Save % order_condition and div_n in error code. disp( sprintf( [ 'Order condition violated in budded cell at ' ... 'time %.0d; order_condition==%d. No division, ' ... 'failed model' ], ... floor( sol.xe( x_col ) ), order_condition ) ); stop_int = 1; cycle = 2000 + 100 * order_condition + div_n; elseif ~budding_condition % If cell is unbudded it can't divide so the model fails; also save % order_condition and div_n information in the cycle error code. disp( sprintf( [ 'Unbudded cell at division at time %0.d; ' ... 'order_condition==%d.' ... 'No division, failed model' ], ... floor( sol.xe( x_col ) ), order_condition ) ); stop_int = 1; cycle = 3000 + 100 * order_condition + div_n; end case 3 % Origins are loaded due to drop in Clbs5,2. % disp('Origins reload'); if plot_on == true disp( sprintf( 'Origins reload at time %.0d', ... floor( sol.xe( x_col ) ) ) ); end model.init( 34 ) = 0; if order_condition == 0 order_condition = 1; end case 4 % Origins fire due to rise in Clbs5,2. if plot_on == true disp( sprintf( [ 'Replication model.initiates, ' ... 'Vi20, Bub2 high at time %.0d' ], ... floor( sol.xe( x_col ) ) ) ); end model.init( 37 ) = ki20_r; model.init( 39 ) = Bub2h; % model.initiation of replication fails unless origins were previously % licensed; this is the 'point of no return'. No distinction % between model.initiation and completion of replication in the % present model! if order_condition == 1 order_condition = 2; end case 5 % Spindle formed due to rise in Clb2. if plot_on == true disp( sprintf( [ 'Spindle formed, Vi20, Bub2 low; ' ... 'Lte1 high at time %.0d' ], ... floor( sol.xe( x_col ) ) ) ); end model.init( 37 ) = ki20_c; model.init( 39 ) = Bub2l; model.init( 38 ) = Lte1h; % Complete spindle formation requires completely replicated % chromosomes, i.e. order_condition==2. It is assumed here that if % the spindle is 'ready' with unreplicated DNA, then this % results in model failure. This assumption could be wrong in % some specific cases - i.e. why couldn't a cell mostly build % the spindle and wait for replication to be complete, as with % HU? So in this case the order criterion is overly stringent. if order_condition == 2 order_condition = 3; end case 6 % Esp1 activated if plot_on == true disp( sprintf( 'Esp1 activated at time %.0d', ... floor( sol.xe( x_col ) ) ) ); end % Esp1 activation is a good thing if the spindle is assembled % with replicated DNA. Otherwise it could be bad - and in % this formulation is unconditionally lethal. Again, some % special cases could be imagined where this rule is too % stringent, but it is generally OK. (example: Spindle is % built, attached to replicated centromeres, but telomeres still % unreplicated. No obvious problem in such a cell to activating % Esp1. But at present there's no distinction between % model.initiation and completion of replication, so the problem does % not arise). if order_condition == 3 order_condition = 4; end case 7 % Cell buds if plot_on == true disp( sprintf( 'Cell buds at time %.0d', floor( sol.xe( x_col ) ) ) ); end budding_condition = true; case 9 % do nothing here case 10 % If the cell is too big, don't repeat the % integration - exit the while loop disp( 'Cell is too big' ); stop_int = 1; cycle = 4000 + 100 * order_condition + div_n; otherwise % A catchall - doesn't seem to ever happen! disp( 'other problem' ); stop_int = 1; cycle = 1000 + div_n; end end end if div_n == 0 disp( 'No divisions' ); elseif div_n == 1 disp( 'Only one division' ); end result = pheno'; save pheno.txt result -double -ascii -tabs; end function [ value, isterminal, direction ] = model_events( t, y, params, times ) % Discrete event detector: % Locate the time when cell divides and stop integration. % Also stop if time runs out, if the cell gets too big, if origins reload or % fire, if spindle is assembled, if Esp1 activates, if cell buds. KEZ = params( 26, 1 ); KEZ2 = params( 26, 4 ); timeout = times( 2 ); % Variable name assignments, of form v_name=y(x) MASS = y( 1 ); Clb2 = y( 3 ); Clb5 = y( 4 ); ESP1 = y( 33 ); ORI = y( 34 ); BUD = y( 35 ); SPN = y( 36 ); % Time runs out value( 1 ) = t - timeout; % Division: cell divides when Clb2 drops below a threshold. value( 2 ) = Clb2 - KEZ; % Origin relicensing due to drop in Clbs5,2. Note this can also happen % coincident with division criterion being met value( 3 ) = Clb2 + Clb5 - KEZ2; % Replication model.initiates due to rise in Clbs5,2; ORI increases through 1. value( 4 ) = ORI - 1; % Spindle starts due to sufficient Clb2; SPN increases through 1. value( 5 ) = SPN - 1; % Esp1 activates value( 6 ) = ESP1 - 0.1; % Cell buds value( 7 ) = BUD - 1; % Cell is too big % Criterion for 'lethal' over-sized cell too_big = 10; value( 10 ) = MASS - too_big; % Stop the integration when value=0 isterminal = [ 1 1 1 1 1 1 1 1 1 1 ]; % Indicates whether the value has to reach zero from above or below. % So increasing through a threshold gives a 1, decreasing gives a -1 direction = [ 1 -1 -1 1 1 1 1 1 1 1 ]; end function test = comp_divs( div_history, n ) [ rows, cols ] = size( div_history ); diffsq = 0; for col = 2:cols diffsq = diffsq ... + ( div_history( rows, col ) - div_history( rows - n, col ) )^2; end mndiffsq = diffsq / ( cols - 3 ); test = sqrt( mndiffsq ); end