https://github.com/aesundstrom/tumor-hypoxia-simulation
Tip revision: f59f05da7ca3ebf5e6969a65cde9ba60f4b0f47f authored by Andrew Sundstrom on 13 March 2017, 21:53:28 UTC
Updated README.
Updated README.
Tip revision: f59f05d
simulator.m
function [] = simulator()
%% initialize operating parameters
initialize_operating_parameters;
%% declare parameters and data structures
% get the name of the simulation from the calling function
global simulation_name;
% define essential simulation arrays
global occupation concentration impact_factor_cond replaceable_cond reproductive_cond fitness_1 fitness_n;
occupation = zeros(x_dim, y_dim, z_dim); % cell types
concentration = zeros(x_dim, y_dim, z_dim, num_particle_types); % particle concentrations
impact_factor_cond = zeros(x_dim, y_dim, z_dim, num_particle_types); % impact factor of each particle type for each cell
replaceable_cond = zeros(x_dim, y_dim, z_dim); % replacement status of each cell
reproductive_cond = zeros(x_dim, y_dim, z_dim); % reproductive status of each cell
fitness_1 = zeros(x_dim, y_dim, z_dim); % local fitness (used in conjunction with occupation; each coordinate has one local fitness value)
fitness_n = zeros(x_dim, y_dim, z_dim, num_cell_types); % neighborhood fitness (each coordinate has a neighborhood fitness value for each cell type)
% define statistics arrays
global population fitness_1_avg fitness_1_std fitness_n_avg fitness_n_std diameter_x diameter_y diameter_z epc wallclock;
population = zeros(num_cell_types, num_iters); % population of each cell type
fitness_1_avg = zeros(num_cell_types, num_iters); % mean local fitness for each cell type
fitness_1_std = zeros(num_cell_types, num_iters); % standard deviation local fitness for each cell type
fitness_n_avg = zeros(num_cell_types, num_iters); % mean neighborhood fitness for each cell type
fitness_n_std = zeros(num_cell_types, num_iters); % standard deviation neighborhood fitness for each cell type
diameter_x = zeros(num_cell_types, num_iters); % global x-extent for each cell type
diameter_y = zeros(num_cell_types, num_iters); % global y-extent for each cell type
diameter_z = zeros(num_cell_types, num_iters); % global z-extent for each cell type
epc = zeros(num_cell_types, num_iters); % global Euler-Poincare characteristic for each cell type
wallclock = zeros(num_iters); % wallclock time for each tick's processing
%% set initial conditions
% define some useful landmarks
global mid_x mid_y mid_z mid_mid_x mid_mid_y mid_mid_z;
mid_x = floor(x_dim / 2);
mid_y = floor(y_dim / 2);
mid_z = floor(z_dim / 2) + 1;
mid_mid_x = floor(x_dim / 4);
mid_mid_y = floor(y_dim / 4);
mid_mid_z = floor(z_dim / 4) + 1;
% initialize particle concentrations by particle type
for pt = 1 : num_particle_types
concentration(:,:,:,pt) = init_concentration(pt);
end
% initialize the cell type occupations
occupation_delay_elapsed = false;
if ~delay_occupation_by
initialize_occupation;
occupation_delay_elapsed = true;
end
%% setup for outputting results
if output_results
% create timestamp
[year, month, day, hour, minute, second] = datevec(now);
years = num2str(year);
months = num2str(month);
days = num2str(day);
hours = num2str(hour);
minutes = num2str(minute);
seconds = num2str(floor(second));
if length(months) == 1
months = strcat('0', months);
end
if length(days) == 1
days = strcat('0', days);
end
if length(hours) == 1
hours = strcat('0', hours);
end
if length(minutes) == 1
minutes = strcat('0', minutes);
end
if length(seconds) == 1
seconds = strcat('0', seconds);
end
ts = sprintf('%s__%s_%s_%s__%s_%s_%s', simulation_name, years, months, days, hours, minutes, seconds);
% create new results directory
global path;
path = strcat('/tmp/simulations/', ts);
mkdir(path);
end
%% simulation loop
global tick;
for tick = 1 : num_iters
%% prologue
% set the timer for this tick
tic;
% set the time scale separation parameter
tau = tick * d_tau;
% initialize once the cell type occupations after the delay
if tick > delay_occupation_by && ~occupation_delay_elapsed
initialize_occupation;
occupation_delay_elapsed = true;
end
% initialize the conditional matrices after the delay
if tick > delay_occupation_by
impact_factor_cond = reshape(impact_factor(occupation,:), x_dim, y_dim, z_dim, num_particle_types);
replaceable_cond = replaceable(occupation);
reproductive_cond = reproductive(occupation);
end
%% phase I: consume and release particles
% if past the occupation delay
if tick > delay_occupation_by
% DEFAULT: consume and release particles according to the cells' respective consumption and release rates
for pt = 1 : num_particle_types
concentration_pt = concentration(:,:,:,pt);
for ct = 1 : num_cell_types
concentration_pt(occupation == ct) = concentration_pt(occupation == ct) * (1 - consume_rate(ct,pt) + release_rate(ct,pt));
end
concentration(:,:,:,pt) = concentration_pt;
end
% CONDITIONAL: for each cell type defined in the conditionals
for cond_key = conditionals.keys
% define the cell type from the conditional key
ct = cond_key{1};
% fetch the conditional block
cond = conditionals(ct);
% for each trigger-action defined for this cell type
for ta = 1 : numel(cond)
% parse out the trigger set and the action set
triggers = cond{ta}{1}; % trigger set
actions = cond{ta}{2}; % action set
% declare a result matrix for this trigger set
ts_result = ones(x_dim, y_dim, z_dim);
% for each trigger in the trigger set
for t = 1 : numel(triggers)
% fetch the trigger out of the trigger set
trigger = triggers{t};
% parse out the trigger elements
trigger_pt = trigger{1}; % this trigger's particle type
trigger_op = trigger{2}; % this trigger's operator
trigger_pc = trigger{3}; % this trigger's particle concentration
% obtain the appropriate concentrations for this trigger's particle type
concentration_trigger_pt = concentration(:,:,:,trigger_pt);
% determine the results of this trigger based on this trigger's operator
switch trigger_op
case '<'
ts_result = ts_result & (occupation == ct) & (concentration_trigger_pt < trigger_pc);
case '>'
ts_result = ts_result & (occupation == ct) & (concentration_trigger_pt > trigger_pc);
case '='
ts_result = ts_result & (occupation == ct) & (concentration_trigger_pt == trigger_pc);
end
end % for each trigger in the trigger set
% for each action defined for this trigger set
for a = 1 : numel(actions)
% fetch the action out of the action set
action = actions{a};
% parse out the action elements
action_op = action{1}; % this action's operator
if ~isequal(action_op, 'a')
action_o1 = action{2}; % this action's operand 1 (e.g., boolean target value, particle type, jump cell type)
end
if ~isequal(action_op, 'a') && ~isequal(action_op, 'j') && ~isequal(action_op, '-') && ~isequal(action_op, '+')
action_o2 = action{3}; % this action's operand 2 (e.g., particle concentration)
end
% for the trigger set result cells, perform the action
switch action_op
case 'a'
occupation(ts_result) = 2;
for pt = 1 : num_particle_types
impact_factor_cond_pt = impact_factor_cond(:,:,:,pt);
impact_factor_cond_pt(ts_result) = impact_factor(2,pt);
impact_factor_cond(:,:,:,pt) = impact_factor_cond_pt;
end
replaceable_cond(ts_result) = replaceable(2);
reproductive_cond(ts_result) = reproductive(2);
case 'j'
occupation(ts_result) = action_o1;
for pt = 1 : num_particle_types
impact_factor_cond_pt = impact_factor_cond(:,:,:,pt);
impact_factor_cond_pt(ts_result) = impact_factor(action_o1,pt);
impact_factor_cond(:,:,:,pt) = impact_factor_cond_pt;
end
replaceable_cond(ts_result) = replaceable(action_o1);
reproductive_cond(ts_result) = reproductive(action_o1);
case '-'
replaceable_cond(ts_result) = action_o1;
case '+'
reproductive_cond(ts_result) = action_o1;
case 'c'
concentration_o1 = concentration(:,:,:,action_o1);
concentration_o1(ts_result) = concentration_o1(ts_result) * (1 - action_o2);
concentration(:,:,:,action_o1) = concentration_o1;
case 'r'
concentration_o1 = concentration(:,:,:,action_o1);
concentration_o1(ts_result) = concentration_o1(ts_result) * (1 + action_o2);
concentration(:,:,:,action_o1) = concentration_o1;
case 'i'
impact_factor_cond_o1 = impact_factor_cond(:,:,:,action_o1);
impact_factor_cond_o1(ts_result) = action_o2;
impact_factor_cond(:,:,:,action_o1) = impact_factor_cond_o1;
end
end % for each action defined for this trigger set
end % for each trigger-action defined for this cell type
end % for each cell type defined in the conditionals
end % if tick > delay_occupation
%% phase II: diffuse particles
% diffuse particles according to the particles' respective diffusion rates
for pt = 1 : num_particle_types
sigma = sqrt(2 * diffusion_rate(pt));
if z_dim > 1
G = gauss3([5 5 5], [sigma sigma sigma]);
else
G = fspecial('gaussian', [5 5], sigma);
end
concentration(:,:,:,pt) = imfilter(concentration(:,:,:,pt), G, 'replicate', 'same', 'conv');
end
% restore concentrations to within their basal bounds
for pt = 1 : num_particle_types
concentration_pt = concentration(:,:,:,pt);
for ct = 1 : num_cell_types
bl = basal_lower(ct, pt);
bu = basal_upper(ct, pt);
concentration_pt((occupation == ct) & (concentration_pt < bl)) = bl;
concentration_pt((occupation == ct) & (concentration_pt > bu)) = bu;
end
concentration(:,:,:,pt) = concentration_pt;
end
%% phase III: compute cell fitness
% if past the occupation delay
if tick > delay_occupation_by
% compute local fitness of each voxel
fitness_1 = zeros(x_dim, y_dim, z_dim);
for i = 1 : x_dim
for j = 1 : y_dim
for k = 1 : z_dim
% get the cell type at this voxel
cell_type = occupation(i,j,k);
% skip vessel and empty cell types, since these cannot obtain a local fitness
if cell_type == 1 || cell_type == 2
continue;
end
% comute local fitness based on particle type concentrations and their respective impact factors
impact_factors = reshape(impact_factor_cond(i,j,k,:), 1, num_particle_types);
concentrations = reshape(concentration(i,j,k,:), num_particle_types, 1);
fitness_1(i,j,k) = impact_factors * concentrations;
end
end
end
% compute neighborhood fitness of each voxel
fitness_n = zeros(x_dim, y_dim, z_dim, num_cell_types);
for i = 1 : x_dim
for j = 1 : y_dim
for k = 1 : z_dim
% get the cell type at this voxel
cell_type = occupation(i,j,k);
% skip vessel cell type, since they cannot be targeted for a new cell type (but empty cells still can)
if cell_type == 1
continue;
end
% compute neighborhood fitness; loop from cell type 3 (post-vessel, post-empty) to the last type
for ct = 3 : num_cell_types
num_neighbors = 0;
sum_neighbors = 0;
for ii = i-1 : i+1
if ii < 1 || ii > x_dim
continue;
end
for jj = j-1 : j+1
if jj < 1 || jj > y_dim
continue;
end
for kk = k-1 : k+1
if kk < 1 || kk > z_dim
continue;
end
if occupation(ii,jj,kk) ~= ct
continue;
end
num_neighbors = num_neighbors + 1;
sum_neighbors = sum_neighbors + fitness_1(ii,jj,kk);
end % for kk
end % for jj
end % for ii
if num_neighbors == 0
fitness_n(i,j,k,ct) = 0;
else
fitness_n(i,j,k,ct) = sum_neighbors / num_neighbors;
end
end % for ct
end % for k
end % for j
end % for i
end % if tick > delay_occupation
%% interlude: compute and plot statistics
% compute statistics
for ct = 1 : num_cell_types
% occupations for this cell type
occupation_ct = occupation == ct;
% population of this cell type
population(ct, tick) = sum(sum(sum(occupation_ct)));
% local fitness of this cell type
fitness_1_avg(ct, tick) = mean(fitness_1(occupation_ct));
fitness_1_std(ct, tick) = std(fitness_1(occupation_ct));
% neighborhood fitness of this cell type
fitness_n_ct = fitness_n(:,:,:,ct);
fitness_n_avg(ct, tick) = mean(fitness_n_ct(occupation > 1));
fitness_n_std(ct, tick) = std(fitness_n_ct(occupation > 1));
% x,y,z-extent for this cell type
[cti, ctj, ctk] = ind2sub(size(occupation_ct), find(occupation_ct));
if numel(cti) == 0
diameter_x(ct, tick) = 0;
else
diameter_x(ct, tick) = max(cti) - min(cti) + 1;
end
if numel(ctj) == 0
diameter_y(ct, tick) = 0;
else
diameter_y(ct, tick) = max(ctj) - min(ctj) + 1;
end
if numel(ctk) == 0
diameter_z(ct, tick) = 0;
else
diameter_z(ct, tick) = max(ctk) - min(ctk) + 1;
end
% Euler-Poincare characteristic for this cell type
if z_dim > 1
epc(ct, tick) = imEuler3d(occupation_ct);
else
epc(ct, tick) = imEuler2d(occupation_ct);
end
end
% plot statistics
if ~mod(tick, plot_every)
plot_statistics();
end
%% phase IV: reproduce cells probabilistically
% if past the occupation delay
if tick > delay_occupation_by
% if it's time to reproduce
if ~mod(tick, reproduce_every)
% probabilistically determine the fate of each voxel
for i = 1 : x_dim
for j = 1 : y_dim
for k = 1 : z_dim
% if this cell type is not replaceable, then skip it
if ~replaceable_cond(i,j,k)
continue;
end
% declare a probability vector; the first two elements (representing vessel and empty cell types) won't be used
probabilities = zeros(1, num_cell_types);
% obtain the probabilities from the neighborhood fitness values; loop from cell type 3 (post-vessel, post-empty) to the last type
for ct = 3 : num_cell_types
probabilities(ct) = fitness_n(i,j,k,ct);
end
% sum the probabilities
sum_probabilities = sum(probabilities);
% if the sum of probabilities is greater than one, then normalize the probabilities to one
shrinkage_factor = 1;
if sum_probabilities > 1
shrinkage_factor = 1 / sum_probabilities;
end
probabilities = shrinkage_factor * probabilities;
% partition the [0,1] interval by the set of probabilities, contiguously placed along the interval, and randomly point into the interval to select the target cell type for this cell; loop from cell type 3 (post-vessel, post-empty) to the last type
essay = rand;
beg_num = 0;
end_num = 0;
target_cell_type = 0;
for ct = 3 : num_cell_types
beg_num = end_num;
end_num = end_num + probabilities(ct);
if essay >= beg_num && essay < end_num
target_cell_type = ct;
break;
end
end
% if the random point lies beyond the last cell type's probability range, then it must become an empty cell
if target_cell_type == 0
target_cell_type = 2;
end
% if any of the neighboring cells are of the target type and are reproductive, then mutate the current cell type to the target cell type
neighbor_is_of_target_cell_type_and_reproductive = false;
for ii = i-1 : i+1
if ii < 1 || ii > x_dim
continue;
end
for jj = j-1 : j+1
if jj < 1 || jj > y_dim
continue;
end
for kk = k-1 : k+1
if kk < 1 || kk > z_dim
continue;
end
if (occupation(ii,jj,kk) == target_cell_type) && reproductive_cond(ii,jj,kk)
neighbor_is_of_target_cell_type_and_reproductive = true;
end
end % for kk
end % for jj
end % for ii
if neighbor_is_of_target_cell_type_and_reproductive
occupation(i,j,k) = target_cell_type;
end
end % for k
end % for j
end % for i
end % reproduce every
end % if tick > delay_occupation
%% epilogue
% record the timer for this tick
wallclock(tick) = toc;
end % for tick
if output_results
% create occupation data file name and save occupation data
occ_data_file = strcat(path, '/', 'occupation.txt');
dlmwrite(occ_data_file, occupation);
% create population data file name and save population data
pop_data_file = strcat(path, '/', 'population.txt');
dlmwrite(pop_data_file, population);
end
end % function simulator