diff --git a/changelog.md b/changelog.md index 8a474f4..1e04321 100644 --- a/changelog.md +++ b/changelog.md @@ -1,9 +1,19 @@ # Opqua Changelog +## v0.2.2 +## 21 July 2021 +- change compositionPLot remove_legend behavior to fix bug +- change pathogenDistanceDf seq_names behavior to fix bug +- reduce mean inoculum from hosts into vectors to reflect malaria cycle +- modify infectHost and infectVector inoculation behavior so that mean_inoculum +does not affect overall transmission rate; each infection now results in at +least 1 pathogen transfer (if not containing and not immune to the pathogen +genome sampled) + ## v0.2.1 ## 1 June 2021 -- Update version tags +- update version tags ## v0.2.0 ## 1 June 2021 @@ -44,3 +54,5 @@ - update tutorials. Done, tested ### TODO: +- correctly update arguments in function documentation and README for +compositionPlot family functions diff --git a/opqua/internal/data.py b/opqua/internal/data.py index 6433227..db97cb8 100644 --- a/opqua/internal/data.py +++ b/opqua/internal/data.py @@ -560,7 +560,8 @@ def pathogenDistanceDf( names = sequences if len(seq_names) > 0: - names = seq_names + new_names = seq_names * int( np.ceil( len(names) / len(seq_names) ) ) + names = new_names[0:len(names)] dis_df = pd.DataFrame( dis_mat, index=names, columns=names ) diff --git a/opqua/internal/host.py b/opqua/internal/host.py index 7df89d7..2b88318 100644 --- a/opqua/internal/host.py +++ b/opqua/internal/host.py @@ -112,44 +112,50 @@ def infectHost(self, host): inoculum by drawing from a Poisson distribution with a mean equal to the mean inoculum size of the organism being infected weighted by each genome's fitness as a fraction of the total in the infector as the - probability of each trial. Each pathogen present in the inoculum will be - added to the infected organism, if it does not have protection from the - pathogen's genome. Fitnesses are computed for the pathogens' genomes in - the infected organism, and the organism is included in the poplation's - infected list if appropriate. + probability of each trial (minimum 1 pathogen transfered). Each pathogen + present in the inoculum will be added to the infected organism, if it + does not have protection from the pathogen's genome. Fitnesses are + computed for the pathogens' genomes in the infected organism, and the + organism is included in the poplation's infected list if appropriate. Arguments: - host -- the host to be infected (Host) + vector -- the vector to be infected (Vector) Returns: whether or not the model has changed state (Boolean) """ changed = False - for genome,fitness in self.pathogens.items(): + + genomes = list( self.pathogens.keys() ) + fitness_weights = [ + self.pathogens[g] / self.sum_fitness for g in genomes + ] + + for _ in range( max( np.random.poisson( + self.population.mean_inoculum_host + ), 1 ) ): + genome = np.random.choice( genomes, p=fitness_weights ) if genome not in host.pathogens.keys() and not any( [ p in genome for p in host.protection_sequences ] - ) and ( np.random.poisson( - self.population.mean_inoculum_host - * fitness / self.sum_fitness, 1 - ) > 0 ): + ): host.acquirePathogen(genome) changed = True return changed def infectVector(self, vector): - """Infect given host with a sample of this vector's pathogens. + """Infect given host with a sample of this host's pathogens. Each pathogen in the infector is sampled as present or absent in the inoculum by drawing from a Poisson distribution with a mean equal to the mean inoculum size of the organism being infected weighted by each genome's fitness as a fraction of the total in the infector as the - probability of each trial. Each pathogen present in the inoculum will be - added to the infected organism, if it does not have protection from the - pathogen's genome. Fitnesses are computed for the pathogens' genomes in - the infected organism, and the organism is included in the poplation's - infected list if appropriate. + probability of each trial (minimum 1 pathogen transfered). Each pathogen + present in the inoculum will be added to the infected organism, if it + does not have protection from the pathogen's genome. Fitnesses are + computed for the pathogens' genomes in the infected organism, and the + organism is included in the poplation's infected list if appropriate. Arguments: vector -- the vector to be infected (Vector) @@ -159,13 +165,19 @@ def infectVector(self, vector): """ changed = False - for genome,fitness in self.pathogens.items(): + + genomes = list( self.pathogens.keys() ) + fitness_weights = [ + self.pathogens[g] / self.sum_fitness for g in genomes + ] + + for _ in range( max( np.random.poisson( + self.population.mean_inoculum_vector + ), 1 ) ): + genome = np.random.choice( genomes, p=fitness_weights ) if genome not in vector.pathogens.keys() and not any( [ p in genome for p in vector.protection_sequences ] - ) and ( np.random.poisson( - self.population.mean_inoculum_vector - * fitness / self.sum_fitness, 1 - ) > 0 ): + ): vector.acquirePathogen(genome) changed = True diff --git a/opqua/internal/plot.py b/opqua/internal/plot.py index 4eb26a0..327748b 100644 --- a/opqua/internal/plot.py +++ b/opqua/internal/plot.py @@ -309,7 +309,7 @@ def compositionPlot( if remove_legend: pd.DataFrame( - labels, columns=['Groups'] + labs, columns=['Groups'] ).to_csv(file_name.split('.')[0]+'_labels.csv') else: handles, labels = ax.get_legend_handles_labels() diff --git a/opqua/internal/vector.py b/opqua/internal/vector.py index 3334a31..85b27c5 100644 --- a/opqua/internal/vector.py +++ b/opqua/internal/vector.py @@ -110,17 +110,17 @@ def acquirePathogen(self, genome): self.population.infected_vectors.append(self) def infectHost(self, host): - """Infect given host with a sample of this host's pathogens. + """Infect given host with a sample of this vector's pathogens. Each pathogen in the infector is sampled as present or absent in the inoculum by drawing from a Poisson distribution with a mean equal to the mean inoculum size of the organism being infected weighted by each genome's fitness as a fraction of the total in the infector as the - probability of each trial. Each pathogen present in the inoculum will be - added to the infected organism, if it does not have protection from the - pathogen's genome. Fitnesses are computed for the pathogens' genomes in - the infected organism, and the organism is included in the poplation's - infected list if appropriate. + probability of each trial (minimum 1 pathogen transfered). Each pathogen + present in the inoculum will be added to the infected organism, if it + does not have protection from the pathogen's genome. Fitnesses are + computed for the pathogens' genomes in the infected organism, and the + organism is included in the poplation's infected list if appropriate. Arguments: vector -- the vector to be infected (Vector) @@ -130,13 +130,19 @@ def infectHost(self, host): """ changed = False - for genome,fitness in self.pathogens.items(): + + genomes = list( self.pathogens.keys() ) + fitness_weights = [ + self.pathogens[g] / self.sum_fitness for g in genomes + ] + + for _ in range( max( np.random.poisson( + self.population.mean_inoculum_host + ), 1 ) ): + genome = np.random.choice( genomes, p=fitness_weights ) if genome not in host.pathogens.keys() and not any( [ p in genome for p in host.protection_sequences ] - ) and ( np.random.poisson( - self.population.mean_inoculum_host - * fitness / self.sum_fitness, 1 - ) > 0 ): + ): host.acquirePathogen(genome) changed = True @@ -149,11 +155,11 @@ def infectVector(self, vector): inoculum by drawing from a Poisson distribution with a mean equal to the mean inoculum size of the organism being infected weighted by each genome's fitness as a fraction of the total in the infector as the - probability of each trial. Each pathogen present in the inoculum will be - added to the infected organism, if it does not have protection from the - pathogen's genome. Fitnesses are computed for the pathogens' genomes in - the infected organism, and the organism is included in the poplation's - infected list if appropriate. + probability of each trial (minimum 1 pathogen transfered). Each pathogen + present in the inoculum will be added to the infected organism, if it + does not have protection from the pathogen's genome. Fitnesses are + computed for the pathogens' genomes in the infected organism, and the + organism is included in the poplation's infected list if appropriate. Arguments: vector -- the vector to be infected (Vector) @@ -163,13 +169,19 @@ def infectVector(self, vector): """ changed = False - for genome,fitness in self.pathogens.items(): + + genomes = list( self.pathogens.keys() ) + fitness_weights = [ + self.pathogens[g] / self.sum_fitness for g in genomes + ] + + for _ in range( max( np.random.poisson( + self.population.mean_inoculum_vector + ), 1 ) ): + genome = np.random.choice( genomes, p=fitness_weights ) if genome not in vector.pathogens.keys() and not any( [ p in genome for p in vector.protection_sequences ] - ) and ( np.random.poisson( - self.population.mean_inoculum_vector - * fitness / self.sum_fitness, 1 - ) > 0 ): + ): vector.acquirePathogen(genome) changed = True diff --git a/opqua/model.py b/opqua/model.py index 6b096ad..882ac7e 100644 --- a/opqua/model.py +++ b/opqua/model.py @@ -363,7 +363,7 @@ def newSetup( mean_inoculum_host = \ 1e2 if mean_inoculum_host is None else mean_inoculum_host mean_inoculum_vector = \ - 1e2 if mean_inoculum_vector is None else mean_inoculum_vector + 1 if mean_inoculum_vector is None else mean_inoculum_vector recovery_rate_host = \ 1e-1 if recovery_rate_host is None else recovery_rate_host recovery_rate_vector = \