def _extract_nutrients(self, available): consumed = Nutrients() uptake_quantity = self._calculate_uptake_rate() * self._timestep internaliseN, internaliseC = self._interalise_choices() # Which elements can be internalised? if internaliseN: consumed.Nf = min(available.Nf, uptake_quantity) if internaliseC: consumed.Cm = min(available.Cm, uptake_quantity) return consumed
def __init__(self, dailyCw, dailyCd, dailyCs, dailyNf, dailyCellulose, regimen_start_h=0., regimen_end_h=math.inf): """ The daily* args specify the quantities of wheatstarch, dextrinised starch and protein that are consumed by the mouse each day. """ super(Constant, self).__init__(regimen_start_h=regimen_start_h, regimen_end_h=regimen_end_h) self.daily_intake = Nutrients(Cw=dailyCw, Cd=dailyCd, Cs=dailyCs, Nf=dailyNf) self.dailyCw = dailyCw self.dailyCd = dailyCd self.dailyCs = dailyCs self.dailyNf = dailyNf self.dailyCellulose = dailyCellulose self.colicCw = dailyCw - si_absorption_wheatstarch( dailyCw) # daily nutrient quantities secreted into colon self.colicCd = dailyCd - si_absorption_dextrinised(dailyCd) self.colicCs = dailyCs - si_absorption_sucrose(dailyCs) self.colicNf = dailyNf - si_absorption_protein(dailyNf) self.colicCellulose = dailyCellulose # daily quantity of nutrients absorbed by the small intestine. self.calculate_SI_behaviour(dailyCw=dailyCw, dailyCd=dailyCd, dailyCs=dailyCs, dailyNf=dailyNf)
def getNutrients(self, time, timestep): """ Returns the nutrient input from the small intestine to the colin given the specified current time. The timestep is also supplied as the period of time that has passed. Note that the timestep should be given as a proportion of an hour, not a day. """ time = float(time) day_cycle = time % 24.0 if day_cycle < 12.0: # Nighttime. inputCw = self._nighttime_hourly(self.colicCw) inputCd = self._nighttime_hourly(self.colicCd) inputNf = self._nighttime_hourly(self.colicNf) inputCellulose = self._nighttime_hourly(self.colicCellulose) else: # Daytime inputCw = self._daytime_hourly(self.colicCw) inputCd = self._daytime_hourly(self.colicCd) inputNf = self._daytime_hourly(self.colicNf) inputCellulose = self._daytime_hourly(self.colicCellulose) # Convert from hourly rates to whatever the time-step is. inputCw *= timestep inputCd *= timestep inputNf *= timestep inputCellulose *= timestep return Nutrients(Cw=inputCw, Cd=inputCd, Nf=inputNf, non_ferm=inputCellulose)
def initialise_nutrient_Store(self, timestep, run_time): """ Sets up the nutrient store along the GIT such that it appears to have been running for ages. :param run_time: given in hours. """ self._nutrientStores = [] currentTime = 0.0 while currentTime < run_time: currentTime += timestep # Nutrients added nutStore = Nutrients() for ni in self.nutrientInputs: nutStore.add(ni.getNutrients(currentTime, timestep)) self._nutrientStores.insert(0, nutStore) # Prepend to start of gut.
def getNutrients(self, time, timestep): time = float(time) day_cycle = time % 24.0 if day_cycle < 12.0: # nighttime, active phase inputCi = self._nighttime_hourly(self.colicCi) else: # daytime, subdued phase inputCi = self._daytime_hourly(self.colicCi) inputCi *= timestep return Nutrients(Ci=inputCi)
def __init__(self, dailyCw, dailyCd, dailyCs, dailyNf, dailyCellulose, dailyCi, dailyCi_inverse, start_time, colicInputDuration, regimen_start_h=0., regimen_end_h=math.inf): """ The daily* args specify the quantities of wheatstarch, dextrinised startch and protein that are consumed by the mouse each day. ColicInputDuration specifies the duration of time for which nutrients are passed from the small intestine into the colon. """ super(TRF, self).__init__(regimen_start_h=regimen_start_h, regimen_end_h=regimen_end_h) self.daily_intake = Nutrients(Cw=dailyCw, Cd=dailyCd, Cs=dailyCs, Nf=dailyNf, Ci=dailyCi) self.colicInputDuration = colicInputDuration self.start_time = start_time self.end_time = ( self.start_time + colicInputDuration ) % 24.0 # modulo in case feeding period spans 24h cycle self.dailyCw = dailyCw self.dailyCd = dailyCd self.dailyCs = dailyCs self.dailyNf = dailyNf self.colicCw = dailyCw - si_absorption_wheatstarch( dailyCw) # Daily quantities of nutrients secreted into colon. self.colicCd = dailyCd - si_absorption_dextrinised(dailyCd) self.colicCs = dailyCs - si_absorption_sucrose(dailyCs) self.colicNf = dailyNf - si_absorption_protein(dailyNf) self.colicCi = dailyCi # Inulin is non-digestible self.colicCi_inverse = dailyCi_inverse - 0.0 # Inulin is non-digestible. self.colicCellulose = dailyCellulose self.hourlyCw = self.colicCw / self.colicInputDuration self.hourlyCd = self.colicCd / self.colicInputDuration self.hourlyNf = self.colicNf / self.colicInputDuration self.hourlyCi = self.colicCi / self.colicInputDuration self.hourlyCi_inverse = self.colicCi_inverse / ( 24.0 - self.colicInputDuration) self.hourlyCellulose = self.colicCellulose / self.colicInputDuration # Daily quantity of nutrients absorbed by the small intestine. self.calculate_SI_behaviour(dailyCw=dailyCw, dailyCd=dailyCd, dailyCs=dailyCs, dailyNf=dailyNf)
def calculate_SI_behaviour(self, dailyCw, dailyCd, dailyCs, dailyNf): """ Daily quantity of nutrients absorbed by the small intestine. """ self.daily_si_absorbed = Nutrients( Cw=si_absorption_wheatstarch(dailyCw), Cd=si_absorption_dextrinised(dailyCd), Cs=si_absorption_sucrose(dailyCs), Nf=si_absorption_protein(dailyNf)) self.daily_colic_input = Nutrients( Cw=dailyCw - self.daily_si_absorbed.Cw, Cd=dailyCd - self.daily_si_absorbed.Cd, Cs=dailyCs - self.daily_si_absorbed.Cs, Nf=dailyNf - self.daily_si_absorbed.Nf) # Conditional assignments to avoid div by zero prop_Cw = self.daily_si_absorbed.Cw / self.daily_intake.Cw if self.daily_intake.Cw != 0 else 0 prop_Cd = self.daily_si_absorbed.Cd / self.daily_intake.Cd if self.daily_intake.Cd != 0 else 0 prop_Cs = self.daily_si_absorbed.Cs / self.daily_intake.Cs if self.daily_intake.Cs != 0 else 0 prop_Nf = self.daily_si_absorbed.Nf / self.daily_intake.Nf if self.daily_intake.Nf != 0 else 0 self.si_absorption_prop = Nutrients(Cw=prop_Cw, Cd=prop_Cd, Cs=prop_Cs, Nf=prop_Nf)
def __init__(self, Cw5, Cd5, Cs5, Nf5, cellulose5, fastProp, fast1, fast2, regimen_start_h=0., regimen_end_h=math.inf): """ Arguments: Cw5, Cd5, Nf5 - the quantities of wheatstarch, dextrinised starch and protein consumed on a non-diet day. fast1 - which day of the week (numbered 1-7) constitutes the first fast day fast2 - same but for second fast day. fastProp - the proportion of a normal diet eaten on a fasting day (between 0 and 1). """ super(FiveTwo, self).__init__(regimen_start_h=regimen_start_h, regimen_end_h=regimen_end_h) self.daily_intake = Nutrients(Cw=Cw5, Cd=Cd5, Cs=Cs5, Nf=Nf5) self._dailyCw5 = Cw5 - si_absorption_wheatstarch(Cw5) self._dailyCd5 = Cd5 - si_absorption_dextrinised(Cd5) self._dailyCs5 = Cs5 - si_absorption_sucrose(Cs5) self._dailyNf5 = Nf5 - si_absorption_protein(Nf5) self._dailyCellulose5 = cellulose5 # Calculate diet day nutrient secretions into the colon. Cw2 = fastProp * Cw5 Cd2 = fastProp * Cd5 Nf2 = fastProp * Nf5 cellulose2 = fastProp * cellulose5 self._dailyCw2 = Cw2 - si_absorption_wheatstarch(Cw2) self._dailyCd2 = Cd2 - si_absorption_dextrinised(Cd2) self._dailyNf2 = Nf2 - si_absorption_protein(Nf2) self._dailyCellulose2 = cellulose2 # Start time is inclusive, end time is exclusive. Ie, if end = 24h, then on hour 24 feeding stops. self._fast1Start = 24.0 * ( fast1 - 1) # minus 1 here so first day of week is zero. self._fast1End = (24.0 * fast1 ) # 1 hour before the start of the next day. self._fast2Start = 24.0 * ( fast2 - 1) # minus 1 here so first day of week is zero. self._fast2End = (24.0 * fast2 ) # 1 hour before the start of the next day. # daily quantity of nutrients absorbed by the small intestine. self.calculate_SI_behaviour(dailyCw=Cw5, dailyCd=Cd5, dailyCs=Cs5, dailyNf=Nf5)
def getNutrients(self, time, timestep): """ Returns the nutrient input from the small intestine to the colin given the specified current time. The timestep is also supplied as the period of time that has passed. Note that the timestep should be given as a proportion of an hour, not a day. """ time = float(time) day_cycle = time % 24.0 two_day_cycle = time % 48.0 # First, check if day or night time. if day_cycle < 12.0: # Nighttime. inputCw = self._nighttime_hourly(self.colicCw) inputCd = self._nighttime_hourly(self.colicCd) inputNf = self._nighttime_hourly(self.colicNf) inputCi = self._nighttime_hourly(self.colicCi) inputCi_inverse = self._nighttime_hourly(self.colicCi_inverse) inputCellulose = self._nighttime_hourly(self.colicCellulose) else: # Daytime inputCw = self._daytime_hourly(self.colicCw) inputCd = self._daytime_hourly(self.colicCd) inputNf = self._daytime_hourly(self.colicNf) inputCi = self._daytime_hourly(self.colicCi) inputCi_inverse = self._daytime_hourly(self.colicCi_inverse) inputCellulose = self._daytime_hourly(self.colicCellulose) # Next, check if current time is fasting day if 24.0 <= two_day_cycle: inputCw *= self.fastProp inputCd *= self.fastProp inputNf *= self.fastProp inputCi = 0.0 # on fasting days, give no inulin. inputCellulose += self.fastProp else: # Feeding day. inputCi_inverse = 0.0 # On feeding days, give no inulin. # Lastly, convert from hourly rates to whatever the time-step is. inputCw *= timestep inputCd *= timestep inputNf *= timestep inputCi *= timestep inputCi_inverse *= timestep inputCellulose *= timestep inputCi += inputCi_inverse # Combine at this point, and deliver to mouse. return Nutrients(Cw=inputCw, Cd=inputCd, Nf=inputNf, Ci=inputCi, non_ferm=inputCellulose)
def getNutrients(self, time, timestep): """ Returns the prebiotic input given the specified current time. The timestep is also supplied as the period of time that has passed. Note that the timestep should be given as a proportion of an hour, not a day. """ time = float(time) cycleTime = time % 24.0 # how far through the feeding cycle we are # default cases if no input from small intestine inputCi = 0.0 # two cases. 1) start time is smaller than end time (easy). # 2) the feeding period spans the 24h modulo point, in which case the end time will be smaller than the start # time; feeding is defined by being greater than the start time or smaller than the end time. if self.start_time <= cycleTime < self.end_time or \ self.end_time <= self.start_time and (self.start_time <= cycleTime or cycleTime < self.end_time): inputCi = self.hourlyCi * timestep return Nutrients(Ci=inputCi)
def step(self, environment): """ Called within the mouse simulator to step the bacteria through its states. """ if self.dead: return Nutrients(), None # Dead bacteria don't do anything. available = environment.clone() self._nutrient_decay() # The cell consumes nutrients, at a decreasing rate (as it enters stress response). internalised = self._internalise_nutrients(available) # Cell consumes resources from the environment. self._limiting_resource = self._calculate_limiting_resource() child = None if self._decide_divide(self._limiting_resource): child = self._divide() if self._decide_die(self._limiting_resource): self._perform_die() return internalised, child
def __init__(self, dailyCm, dailyNm, regimen_start_h=0., regimen_end_h=math.inf): super(Mucin, self).__init__(regimen_start_h=regimen_start_h, regimen_end_h=regimen_end_h) self.daily_intake = Nutrients(Cm=dailyCm, Nm=dailyNm) # convert daily mucus secretions into hourly quantities. self.dailyCm = dailyCm self.dailyNm = dailyNm self.hourlyCm = dailyCm / 24.0 self.hourlyNm = dailyNm / 24.0 self.calculate_SI_behaviour(dailyCw=0.0, dailyCd=0.0, dailyCs=0.0, dailyNf=0.0) self.daily_colic_input.Cm = dailyCm self.daily_colic_input.Nm = dailyNm
def __init__(self, dailyCw, dailyCd, dailyCs, dailyNf, dailyCellulose, dailyCi, dailyCi_inverse, fastProp, regimen_start_h=0., regimen_end_h=math.inf): """ The daily* args specify the quantities of wheat starch, dextrinised starch and protein that are consumed by the mouse each day. ColicInputDuration specifies the duration of time for which nutrients are passed from the small intestine into the colon. """ super(AlternateDay, self).__init__(regimen_start_h=regimen_start_h, regimen_end_h=regimen_end_h) self.daily_intake = Nutrients(Cw=dailyCw, Cd=dailyCd, Cs=dailyCs, Nf=dailyNf, Ci=dailyCi, non_ferm=dailyCellulose) self.colicCw = dailyCw - si_absorption_wheatstarch( dailyCw) # Daily quantities of nutrients secreted into colon. self.colicCd = dailyCd - si_absorption_dextrinised(dailyCd) self.colicCs = dailyCs - si_absorption_sucrose(dailyCs) self.colicNf = dailyNf - si_absorption_protein(dailyNf) self.colicCi = dailyCi # Inulin is non-digestible self.colicCi_inverse = dailyCi_inverse # Inulin is non-digestible. self.colicCellulose = dailyCellulose # Non-digestible. self.fastProp = fastProp # Alter the proportion of nutrients available between 0 and 1 # daily quantity of nutrients absorbed by the small intestine. self.calculate_SI_behaviour(dailyCw=dailyCw, dailyCd=dailyCd, dailyCs=dailyCs, dailyNf=dailyNf)
def getNutrients(self, time, timestep): """ Returns the nutrient input from the small intestine to the colin given the specified current time. The timestep is also supplied as the period of time that has passed. Note that the timestep should be given as a proportion of an hour, not a day. """ cycle_week = time % 168.0 # How far through the week the current time is, in hours. # Defaults, adjusted as needed. dailyCw = self._dailyCw5 # Assume on a non-fast day to begin with. dailyCd = self._dailyCd5 dailyNf = self._dailyNf5 dailyCellulose = self._dailyCellulose5 if self._in_fast1(cycle_week) or self._in_fast2( cycle_week): # Check for 2-day diet. dailyCw = self._dailyCw2 dailyCd = self._dailyCd2 dailyNf = self._dailyNf2 dailyCellulose = self._dailyCellulose2 cycle24 = time % 24.0 if cycle24 < 12.0: # Nighttime hourlyCw = self._nighttime_hourly(dailyCw) hourlyCd = self._nighttime_hourly(dailyCd) hourlyNf = self._nighttime_hourly(dailyNf) hourlyCellulose = self._nighttime_hourly(dailyCellulose) else: # Daytime hourlyCw = self._daytime_hourly(dailyCw) hourlyCd = self._daytime_hourly(dailyCd) hourlyNf = self._daytime_hourly(dailyNf) hourlyCellulose = self._daytime_hourly(dailyCellulose) inputCw = hourlyCw * timestep inputCd = hourlyCd * timestep inputNf = hourlyNf * timestep inputCellulose = hourlyCellulose * timestep return Nutrients(Cw=inputCw, Cd=inputCd, Nf=inputNf, non_ferm=inputCellulose)
def getNutrients(self, time, timestep): """ Returns the nutrient input from the small intestine to the colin given the specified current time. The timestep is also supplied as the period of time that has passed. Note that the timestep should be given as a proportion of an hour, not a day. """ feedingPeriod = 24.0 time = float(time) cycleTime = time % 24.0 # How far through the feeding cycle we are # Default cases if no input from small intestine inputCw = 0.0 inputCd = 0.0 inputNf = 0.0 inputCi = 0.0 inputCi_inverse = 0.0 inputCellulose = 0. # Two cases. 1) start time is smaller than end time (easy). # 2) the feeding period spans the 24h modulo point, in which case the end time will be smaller than the start # time; feeding is defined by being greater than the start time or smaller than the end time. if self.start_time <= cycleTime < self.end_time or \ self.end_time <= self.start_time and (self.start_time <= cycleTime or cycleTime < self.end_time): inputCw = self.hourlyCw * timestep inputCd = self.hourlyCd * timestep inputNf = self.hourlyNf * timestep inputCi = self.hourlyCi * timestep inputCellulose = self.hourlyCellulose * timestep else: # Fasting time inputCi_inverse = self.hourlyCi_inverse * timestep inputCi += inputCi_inverse # Combine at this point, and deliver to mouse. return Nutrients(Cw=inputCw, Cd=inputCd, Nf=inputNf, Ci=inputCi, non_ferm=inputCellulose)
def getNutrients(self, time, timestep): inputCm = self.hourlyCm * timestep inputNm = self.hourlyNm * timestep return Nutrients(Cm=inputCm, Nm=inputNm) # nutrients not supplied default to zero.
def execute(self, endtime, timestep=0.1, verbose=False, bacteria_graphing_start_time=float('inf'), graphPath=None): """ Called to execute this simulation of a mouse. Execution ceases after `endtime` hours. """ def inoculum(): """ Inputs new bacteria of each guild into the GIT. """ rateDay = inoculation_rate * scale_factor rateHour = rateDay / 24.0 # This gives a probability of insertion of a bug every time step. rateTimestep = rateHour * timestep new_bacteria = [ ] # Compile list of new bacteria, which is shuffled and then placed at start of bacteria # if rateTimestep > 1.0: # raise Exception('Cannot insert more than 1 bug per timestep (with current code).') bugs_to_add = rateTimestep while random.random() <= bugs_to_add and init_Grf != 0: new_bacteria.append( gutsim.bacteria.Guild_rf_f(timestep=timestep)) bugs_to_add -= 1 bugs_to_add = rateTimestep while random.random() <= bugs_to_add and init_Grm != 0: new_bacteria.append( gutsim.bacteria.Guild_rm_m(timestep=timestep)) bugs_to_add -= 1 bugs_to_add = rateTimestep while random.random() <= bugs_to_add and init_Gpf != 0: new_bacteria.append( gutsim.bacteria.Guild_pf_f(timestep=timestep)) bugs_to_add -= 1 bugs_to_add = rateTimestep while random.random() <= bugs_to_add and init_Gpm != 0: new_bacteria.append( gutsim.bacteria.Guild_pm_m(timestep=timestep)) bugs_to_add -= 1 bugs_to_add = rateTimestep while random.random() <= bugs_to_add and init_Gmm != 0: new_bacteria.append( gutsim.bacteria.Guild_m_m(timestep=timestep)) bugs_to_add -= 1 bugs_to_add = rateTimestep while random.random() <= bugs_to_add and init_Gmf != 0: new_bacteria.append( gutsim.bacteria.Guild_mf_f(timestep=timestep)) bugs_to_add -= 1 shuffle(new_bacteria) # Shuffle the additional bugs. new_bacteria.extend( self._bacteria) # Insert new bacteria at start. self._bacteria = new_bacteria def bacteria_peristalsis(): """ Represents peristaliss-driven re-ordering of bacteria in gut. The distance that the shuffling attempts to move each bacteria by is drawn from a gaussian distribution. The spread of the distribution can be altered to be rate-limited by the timestep. Useful literature: Roberts RR, Murphy JF, Young HM, Bornstein JC. Development of colonic motility in the neonatal mouse-studies using spatiotemporal maps. Am J Physiol-Gastr L. 2007;292(3):G930-G8. """ # Calculate the standard deviation of locations that bacteria may be shuffled by due to peristalsis events. # Peristalsis presents a probability distribution representing the post-peristalsis location each bacteria # could end up in. This is represented by a gaussian curve, centred on each bacterium's current location. # start by calculating how many peristalsis events happen per time step. Gaussian standard dev represents # effect of shuffling, so we convert from distance in gut to number of bacteria places (they are ordered). # Lastly, time is accounted for. distance_per_bacteria = gut_length / len(self._bacteria) sigma = bacteria_peristalsis_rate / distance_per_bacteria # Num of bacteria representing the desired dist. diffusion_rate = peristalsis_rate * timestep # How many peristalsis events occur in each time step. sigma *= diffusion_rate # Adjust for number of peristalsis events in each time step. # Start the shuffling. shuffled = [ ] # Will contain tuples: (post-shuffle movement, bacteria object). for index, bug in enumerate(self._bacteria): pos = float(index) + random.gauss( 0, sigma) # Current position + some random quantity. shuffled.append((pos, bug)) # Sorts by the first item in the tuples that the list contains. sort = sorted(shuffled, key=lambda tmp: tmp[0]) self._bacteria = [tup[1] for tup in sort ] # Pull out the bacteria objects. def nutrient_peristalsis(): """ Moves nutrients between nutrient stores; a low level of mixing resulting from peristalsis. """ # Gaussian diffusion represent diffusion as a result of peristalsis, IE, how much diffusion results from # each peristalsis event. This variable indicates how many peristalsis events are represented in the current # time step (time step may represent more than one peristalsis event). diffusion_rate = peristalsis_rate * timestep sigma_time_adjusted = nutrient_peristalsis_rate * diffusion_rate num_nutrient_stores = len(self._nutrientStores) length_per_nutrient_store = gut_length / num_nutrient_stores # Create a new store, which will be populated with nutrients. new_nutrient_store = [Nutrients() for _ in self._nutrientStores] # Location of this store along the gut. Conceptualized as follows. Nutrient sources ordered sequentially. # Their locations defined as cx. For integration, need to know the lower and upper boundaries, which # are given in factors of l (l=length of nutrient source in the gut, and can vary with number of # nutrient sources). # # 0l 1l 2l 3l 4l 5l # ------------------------------------------------------------------- # | c0 | c1 | c2 | c3 | c4 | ... # ------------------------------------------------------------------- # # Normalized gaussian curve integrates to 1, hence its integral describes the diffusion of nutrients # into a specified length of gut. # Pre-compute the diffusion quantities by index distance. This is an efficiency saving. Since the # distribution changes only for the number of nutrient stores, which only changes between time steps, # the same distribution applies for every nutrient store in a timestep. # Need compute only one half of the distribution, as it is symmetrical. diffusion_by_index_difference = [None] * len(self._nutrientStores) # Declared here for efficiency. The lower bound for nutrient source n+1 = the supper bound for nutrient # source n (so don't have to re-compute it). sink_upper_distance = None for k in range(len(self._nutrientStores)): loc_source = 0.5 * length_per_nutrient_store if sink_upper_distance is None: # No boundaries have been calculated for first nutrient source. sink_lower_distance = ( k * length_per_nutrient_store) - loc_source else: # Adopt upper boundary of the previous nutrient source location. sink_lower_distance = sink_upper_distance sink_upper_distance = ( (k + 1) * length_per_nutrient_store) - loc_source integral_lower = stats.phi_gauss(sink_lower_distance, mu=0, sigma=sigma_time_adjusted) integral_upper = stats.phi_gauss(sink_upper_distance, mu=0, sigma=sigma_time_adjusted) proportion_diffused = integral_upper - integral_lower diffusion_by_index_difference[k] = proportion_diffused # Diffusion only performed over ranges where a substanital difference in concentration is incurred. # This saves computational expense, and makes little difference to simulation dynamics. # Here we ascertain the distance over which (meaningful) diffusion will be performed. negligible_diffusion_distance = 0 negligible_diffusion_threshold = 0.05 / len(self._nutrientStores) for i, diff in enumerate(diffusion_by_index_difference): if diff < negligible_diffusion_threshold: # Not worth performing diffusion beyond this spatial range. negligible_diffusion_distance = i break # Scan through each existing nutrient store, and distribute nutrients for i, n_source in enumerate(self._nutrientStores): # Proportion of nutrient store contents not moved anywhere because Gaussian stretches from -Inf to Inf. # This will be retained in the current nutrient store location. remaining = 1.0 # Start from difference of 1, 0 = the source, handled separately below. # Diffusion distribution is symmetrical, so calculate both sides of i (getting progressively further # away) simultaneously. Is more efficient, as not duplicating calculations. for index_difference in range(1, negligible_diffusion_distance): proportion_diffused = diffusion_by_index_difference[ index_difference] # use lookup table. move = n_source.proportion_query(proportion_diffused) i_low = i - index_difference if i_low >= 0: new_nutrient_store[i - index_difference].add(move) remaining -= proportion_diffused i_high = i + index_difference if i_high <= len(self._nutrientStores) - 1: new_nutrient_store[i + index_difference].add(move) remaining -= proportion_diffused # Any nutrient not already dealt with (Gaussian stretches from -Inf to Inf) is retained move = n_source.proportion_query(remaining) new_nutrient_store[i].add(move) self._nutrientStores = new_nutrient_store self.logger = gutsim.vessel.Logger( vessel=self, bacteria_graphing_start_time=bacteria_graphing_start_time, graphPath=graphPath) self.initialise_nutrient_Store(timestep, 24.0) # Initialise bacteria in the simulation for _ in range(init_Grf): self._bacteria.append( gutsim.bacteria.Guild_rf_f(timestep=timestep)) for _ in range(init_Grm): self._bacteria.append( gutsim.bacteria.Guild_rm_m(timestep=timestep)) for _ in range(init_Gpf): self._bacteria.append( gutsim.bacteria.Guild_pf_f(timestep=timestep)) for _ in range(init_Gpm): self._bacteria.append( gutsim.bacteria.Guild_pm_m(timestep=timestep)) for _ in range(init_Gmm): self._bacteria.append(gutsim.bacteria.Guild_m_m(timestep=timestep)) for _ in range(init_Gmf): self._bacteria.append( gutsim.bacteria.Guild_mf_f(timestep=timestep)) shuffle(self._bacteria ) # Ensure bacteria are well mixed prior to simulation. print('------- Nutrient inputs --------') for regime in self.nutrientInputs: print('hourly nutrient inputs:') print(regime.regime_string()) print('--------------------------------') current_time = 0.0 # Cumulative count of how much digesta and bacteria should have been excreted. This is stored as float, and # excretion takes place when threshold of a nutrient store of sufficient size (or a whole bacteria) is reached. digesta_to_excrete = 0.0 bacteria_to_excrete_cumulative = 0.0 self.logger.bacterGraphs = bacteria_graphing_start_time self.logger.log(self._bacteria, current_time) # Step through time while current_time < endtime: current_time += timestep # Nutrients added. New nutrients (from upper GI tract and mucin) are added to new object, and added to front # of nutrient store list. # Note that mucin is then redistributed along the length of the gut. nutStore = Nutrients() for ni in self.nutrientInputs: if ni.is_active(current_time): nutStore.add(ni.getNutrients(current_time, timestep)) # Clone the nutrient record for logging. nutrient_input = nutStore.clone() # Put new nutrient record at start of GIT. self._nutrientStores.insert(0, nutStore) # Secrete mucus evenly along the entire GIT. Cm = nutStore.Cm Nm = nutStore.Nm nutStore.Cm = 0.0 nutStore.Nm = 0.0 Cmi = float(Cm) / len( self._nutrientStores) # Secrete mucus uniformly along GIT. Nmi = float(Nm) / len(self._nutrientStores) for ni in self._nutrientStores: ni.add_quantities(Cm=Cmi, Nm=Nmi) if verbose: totNutrients = Nutrients() for n in self._nutrientStores: totNutrients.add(n) print('ct = ' + str(current_time) + ' ' + self.logger.string_state() + '\n\t' + str(totNutrients)) inoculum() if len(self._bacteria) > 0: # No point shuffling nothing. bacteria_peristalsis() if len(self._nutrientStores) > 0: nutrient_peristalsis() # Step each bacteria. Each bacteria is assigned a nutrient store from which to extract nutrients. # There are likely multiple bacteria feeding from each store. bPerNS = float(len(self._bacteria)) / len(self._nutrientStores) # Build up a new list of bacteria, representing the GIT, as the current list is iterated. nextGIT = [] # Scale nutrients to the desired level (affects total number of bacteria). # This is reversed after bacteria have consumed and grown (see below) nutStore.scale_by_factor(scale_factor) for ns in self._nutrientStores: ns.scale_by_factor(scale_factor) for i, b in enumerate(self._bacteria): # Calculate which index in the nutrients store this bacteria corresponds to. # Int used here always rounds down. # Even if bPerNS is not a whole number, bacteria will broadly still access the correct nutrient store. iNS = int(i / bPerNS) # Returns nutrients consumed, and either a new bacteria object or None. consumed, child = b.step(self._nutrientStores[iNS]) self._nutrientStores[iNS].subtract(consumed) non_absorbable_organic_matter = consumed.total_fermentable_quantity( ) * non_absorbable_organic_matter_generation_rate self._nutrientStores[iNS].add_quantities( other_organic_matter=non_absorbable_organic_matter) # New bacteria go in front of the current bacterium. if child is not None: nextGIT.append(child) nextGIT.append(b) # Add the current bacterium also. # Update for next time step. self._bacteria = nextGIT # Scale nutrient quantities back to grams. for ns in self._nutrientStores: ns.scale_by_factor(1.0 / scale_factor) # Perform mouse defecation. # Awake for first 12 hours of day (nighttime; mice are nocturnal). # Having to convert to ints here, because floats and modulo operator are incompatible. if int(current_time * 10) % 240 < 120: excrete_timestep = excretion_rate * timestep else: # During the light phase (less activity), excretion rate drops to half of active phase. # This gives 2/3 vs 1/3 pattern of total excretion across these phases. excrete_timestep = excretion_rate * 0.5 * timestep digesta_weight = sum( [ns.total_digesta_weight() for ns in self._nutrientStores]) # If the GIT's carrying capacity is exceeded, increase the excretion rate. if excess_digesta(digesta_weight): excrete_timestep *= 5.0 # Value of 5 is arbitrarily chosen. # Keep a cumulative count of how much digesta should have been excreted. When the quantity of nutrient # in the last nutrient store is greater than this cumulative count, delete the nutrient store. digesta_to_excrete += digesta_weight * excrete_timestep # Can't continue to delete if there's nothing left while len(self._nutrientStores) > 0 and \ self._nutrientStores[-1].total_digesta_weight() <= digesta_to_excrete: digesta_to_excrete -= self._nutrientStores[ -1].total_digesta_weight() del (self._nutrientStores[-1]) # Quantity to be excreted bacteria_to_excrete_cumulative += excrete_timestep * len( self._bacteria) bacteria_excreted = int(bacteria_to_excrete_cumulative) if bacteria_excreted > 0: # Safety. list[-0:] = [] will delete the entire list. self.bacteria_excreted_logging += bacteria_excreted # Used for logging. bacteria_to_excrete_cumulative -= float(bacteria_excreted) self._bacteria[-bacteria_excreted:] = [] # Perform logging of bacterial numbers. self.logger.log(self._bacteria, current_time, nutrient_input=nutrient_input)
def nutrient_peristalsis(): """ Moves nutrients between nutrient stores; a low level of mixing resulting from peristalsis. """ # Gaussian diffusion represent diffusion as a result of peristalsis, IE, how much diffusion results from # each peristalsis event. This variable indicates how many peristalsis events are represented in the current # time step (time step may represent more than one peristalsis event). diffusion_rate = peristalsis_rate * timestep sigma_time_adjusted = nutrient_peristalsis_rate * diffusion_rate num_nutrient_stores = len(self._nutrientStores) length_per_nutrient_store = gut_length / num_nutrient_stores # Create a new store, which will be populated with nutrients. new_nutrient_store = [Nutrients() for _ in self._nutrientStores] # Location of this store along the gut. Conceptualized as follows. Nutrient sources ordered sequentially. # Their locations defined as cx. For integration, need to know the lower and upper boundaries, which # are given in factors of l (l=length of nutrient source in the gut, and can vary with number of # nutrient sources). # # 0l 1l 2l 3l 4l 5l # ------------------------------------------------------------------- # | c0 | c1 | c2 | c3 | c4 | ... # ------------------------------------------------------------------- # # Normalized gaussian curve integrates to 1, hence its integral describes the diffusion of nutrients # into a specified length of gut. # Pre-compute the diffusion quantities by index distance. This is an efficiency saving. Since the # distribution changes only for the number of nutrient stores, which only changes between time steps, # the same distribution applies for every nutrient store in a timestep. # Need compute only one half of the distribution, as it is symmetrical. diffusion_by_index_difference = [None] * len(self._nutrientStores) # Declared here for efficiency. The lower bound for nutrient source n+1 = the supper bound for nutrient # source n (so don't have to re-compute it). sink_upper_distance = None for k in range(len(self._nutrientStores)): loc_source = 0.5 * length_per_nutrient_store if sink_upper_distance is None: # No boundaries have been calculated for first nutrient source. sink_lower_distance = ( k * length_per_nutrient_store) - loc_source else: # Adopt upper boundary of the previous nutrient source location. sink_lower_distance = sink_upper_distance sink_upper_distance = ( (k + 1) * length_per_nutrient_store) - loc_source integral_lower = stats.phi_gauss(sink_lower_distance, mu=0, sigma=sigma_time_adjusted) integral_upper = stats.phi_gauss(sink_upper_distance, mu=0, sigma=sigma_time_adjusted) proportion_diffused = integral_upper - integral_lower diffusion_by_index_difference[k] = proportion_diffused # Diffusion only performed over ranges where a substanital difference in concentration is incurred. # This saves computational expense, and makes little difference to simulation dynamics. # Here we ascertain the distance over which (meaningful) diffusion will be performed. negligible_diffusion_distance = 0 negligible_diffusion_threshold = 0.05 / len(self._nutrientStores) for i, diff in enumerate(diffusion_by_index_difference): if diff < negligible_diffusion_threshold: # Not worth performing diffusion beyond this spatial range. negligible_diffusion_distance = i break # Scan through each existing nutrient store, and distribute nutrients for i, n_source in enumerate(self._nutrientStores): # Proportion of nutrient store contents not moved anywhere because Gaussian stretches from -Inf to Inf. # This will be retained in the current nutrient store location. remaining = 1.0 # Start from difference of 1, 0 = the source, handled separately below. # Diffusion distribution is symmetrical, so calculate both sides of i (getting progressively further # away) simultaneously. Is more efficient, as not duplicating calculations. for index_difference in range(1, negligible_diffusion_distance): proportion_diffused = diffusion_by_index_difference[ index_difference] # use lookup table. move = n_source.proportion_query(proportion_diffused) i_low = i - index_difference if i_low >= 0: new_nutrient_store[i - index_difference].add(move) remaining -= proportion_diffused i_high = i + index_difference if i_high <= len(self._nutrientStores) - 1: new_nutrient_store[i + index_difference].add(move) remaining -= proportion_diffused # Any nutrient not already dealt with (Gaussian stretches from -Inf to Inf) is retained move = n_source.proportion_query(remaining) new_nutrient_store[i].add(move) self._nutrientStores = new_nutrient_store
def diet_analysis(mice): print('\nPERFORMING DIET ANALYSIS\n') diet_code_file = open('diet-analysis/diet-composition-per-mouse.csv', 'w') relNutFile = open('diet-analysis/nutrient_rel_abundances.csv', 'w') relNutFile.write( '#{:8s},{:16s},{:16s},{:16s},'.format( 'mouse-ID', 'carb-intake-KJ/d', 'prot-intake-KJ/d', 'fat-intake-KJ/d') + '{:7s},{:7s},{:7s},{:7s},{:7s},'.format( 'relCw', 'relCd', 'relCm', 'relNf', 'relNm') + '{:7s},{:7s},{:7s},{:7s},{:7s},'.format('inCw', 'inCd', 'inCm', 'inNf', 'inNm') + '{:7s},{:7s},{:7s},{:7s},{:7s},'.format('siCw', 'siCd', 'siCm', 'siNf', 'siNm') + '{:7s},{:7s},{:7s},{:7s},{:7s},'.format('siPCw', 'siPCd', 'siPCm', 'siPNf', 'siPNm') + '{:7s},{:7s},{:7s},{:7s},{:7s},'.format( 'colicCw', 'colicCd', 'colicCm', 'colicNf', 'colicNm') + '{:7s},'.format('Nf:Nm') + '{:14s},{:14s},{:14s},{:14},{:14},{:14s},'.format( 'cecum_relCw', 'cecum_relCd', 'cecum_relCmuc', 'cecum_relCcas', 'cecum_relNmuc', 'cecum_relNcas') + '\n') diet_code_file.write( '%5s, %10s, %6s, %6s, %6s, %6s, %6s\n' % ('#ID', 'diet code', 'Nf_g', 'Cw_g', 'Cd_g', 'Nm_g', 'Cm_g')) for i, mouse_num in enumerate(mice.keys()): mouse = mice[mouse_num] # This code is used for writing nutrient relative abundances to the file system. ci = mouse.daily_wheatstarch_kj + mouse.daily_dextrinised_kj + mouse.daily_sucrose_kj pi = mouse.daily_nitrogen_kj fi = mouse.daily_fat_kj # Fat intake # Instantiate variables, values cumulatively added based on each nutrient input regimen. intake = Nutrients() colic = Nutrients() si_absorb = Nutrients() si_prop = Nutrients() for nIn in mouse.nutrientInputs: intake.add( nIn.dailyIntake ) # A Nutrients object representing daily generation of nutrients. colic.add(nIn.daily_colic_input) si_absorb.add(nIn.daily_si_absorbed) si_prop.add(nIn.si_absorption_prop) # These are by weight of macronutrient's carbon/nitrogen atoms. totC_weight = intake.carbon_content() totN_weight = intake.nitrogen_content() # These are percentages by weight of macronutrient carbon content. rel_carbon_Cw = 100 * nutrients.carbon_content_carbohydrate( intake.Cw) / totC_weight rel_carbon_Cd = 100 * nutrients.carbon_content_carbohydrate( intake.Cd) / totC_weight rel_carbon_Cs = 100 * nutrients.carbon_content_carbohydrate( intake.Cs) / totC_weight rel_carbon_Cm = 100 * nutrients.carbon_content_carbohydrate( intake.Cm) / totC_weight rel_nitrogen_Nf = 100 * nutrients.nitrogen_content_protein( intake.Nf) / totN_weight rel_nitrogen_Nm = 100 * nutrients.nitrogen_content_protein( intake.Nm) / totN_weight ratio_nitrogen_NFNm = intake.Nf / intake.Nm # These are percentages by weight of macronutrient carbon content. # Considers macronutrients available to bacteria, i.e. feed macronutrients post-small intestine. cecum_totC_weight = colic.carbon_content( ) # These are by weight of macronutrient's carbon/nitrogen atoms. cecum_totN_weight = colic.nitrogen_content() print('cecum C weight ' + str(cecum_totC_weight) + ' ; total weight = ' + str(totC_weight)) cecum_rel_carbon_Cw = 100 * nutrients.carbon_content_carbohydrate( colic.Cw) / cecum_totC_weight cecum_rel_carbon_Cd = 100 * nutrients.carbon_content_carbohydrate( colic.Cd) / cecum_totC_weight cecum_rel_carbon_Cs = 100 * nutrients.carbon_content_carbohydrate( colic.Cs) / cecum_totC_weight cecum_rel_carbon_mucin = 100 * \ (nutrients.carbon_content_carbohydrate(colic.Cm) + nutrients.carbon_content_protein(colic.Nm)) \ / cecum_totC_weight # Some carbon comes from casein too. cecum_rel_carbon_cas = 100 * nutrients.carbon_content_protein( colic.Nf) / cecum_totC_weight cecum_rel_nitrogen_mucin = 100 * nutrients.nitrogen_content_protein( colic.Nm) / cecum_totN_weight cecum_rel_nitrogen_cas = 100 * nutrients.nitrogen_content_protein( colic.Nf) / cecum_totN_weight relNutFile.write( '{:9d},{:10.10f},{:10.10f},{:10.10f},'.format( i, ci, pi, fi) # Mouse index, then carb, prot and fat intakes # Relative abundances of carbon and nitrogen, by carbon atom weight of INTAKE/secreted quantities. + '{:.17f},{:.17f},{:.17f},{:.17f},{:.17f},'.format( rel_carbon_Cw, rel_carbon_Cd, rel_carbon_Cm, rel_nitrogen_Nf, rel_nitrogen_Nm) # Nutrients eaten + '{:.17f},{:.17f},{:.17f},{:.17f},{:.17f},'.format( intake.Cw, intake.Cd, intake.Cm, intake.Nf, intake.Nm) # Absolute quantity absorbed by the SI + '{:.17f},{:.17f},{:.17f},{:.17f},{:.17f},'.format( si_absorb.Cw, si_absorb.Cd, si_absorb.Cm, si_absorb.Nf, si_absorb.Nm) # Proportion of eaten nutrients absorbed by the SI + '{:.17f},{:.17f},{:.17f},{:.17f},{:.17f},'.format( si_prop.Cw, si_prop.Cd, si_prop.Cm, si_prop.Nf, si_prop.Nm) # Quantity reaching the colon. + '{:.17f},{:.17f},{:.17f},{:.17f},{:.17f},'.format( colic.Cw, colic.Cd, colic.Cm, colic.Nf, colic.Nm) + '{:.17f},'.format(ratio_nitrogen_NFNm) # Relative abundances of carbon and nitrogen (by atom weight, not total macronutrient weight), given # availability to bacteria. i.e., post-small intestine. + '{:14.2f},{:14.2f},{:14.2f},{:14.2f},{:14.2f},{:14.2f}'.format( cecum_rel_carbon_Cw, cecum_rel_carbon_Cd, cecum_rel_carbon_mucin, cecum_rel_carbon_cas, cecum_rel_nitrogen_mucin, cecum_rel_nitrogen_cas) + '\n') diet_code_file.write('%5d, %10s, %6f, %6f, %6f, %6f, %6f\n' % (i, mouse.dietCode, intake.Nf, intake.Cw, intake.Cd, intake.Nm, intake.Cm)) diet_code_file.close() relNutFile.close()