diff --git a/README.md b/README.md index 8f72c2d..96d77c6 100644 --- a/README.md +++ b/README.md @@ -718,8 +718,8 @@ _Keyword arguments:_ - **kwargs -- setup parameters and values, which may include the following: - num_loci -- length of each pathogen genome string (int > 0) - possible_alleles -- set of possible characters in all genome string, or - at each position in genome string (String or list of Strings with - num_loci elements) + at each position in genome string; to specify lists in parameter files, use + prefix `#LIST:` (String or list of Strings with num_loci elements) - fitnessHost -- function that evaluates relative fitness in head-to-head competition for different genomes within the same host (function object, takes a String argument and returns a number >= 0) @@ -860,7 +860,7 @@ _Keyword arguments:_ #### saveSetup ```python -saveSetup(setup_name, save_to_file) +saveSetup(setup_id, save_to_file) ``` @@ -868,20 +868,20 @@ Saves Setup parameters to given file location as a CSV file. Functions (e.g. fitness functions) cannot be saved in this format. _Arguments:_ -- setup_name -- name of setup used as a key in setups dictionary +- setup_id -- name of setup used as a key in setups dictionary - save_to_file -- file path and name to save parameters under (String) #### loadSetup ```python -loadSetup(setup_name, file, preset=None) +loadSetup(setup_id, file, preset=None) ``` Loads Setup parameters from CSV file at given location. _Arguments:_ -- setup_name -- name of setup to be used as a key in setups dictionary +- setup_id -- name of setup to be used as a key in setups dictionary - file -- file path to CSV file with parameters (String) _Keyword arguments:_ @@ -1541,7 +1541,7 @@ _Returns:_ #### newPopulation ```python -newPopulation(id, setup_name, num_hosts=100, num_vectors=100) +newPopulation(id, setup_id, num_hosts=100, num_vectors=100) ``` @@ -1551,7 +1551,7 @@ If population ID is already in use, appends _2 to it _Arguments:_ - id -- unique identifier for this population in the model (String) -- setup_name -- setup object with parameters for this population (Setup) +- setup_id -- setup object with parameters for this population (Setup) _Keyword Arguments:_ - num_hosts -- number of hosts to initialize population with (default 100; @@ -1648,7 +1648,7 @@ _Arguments:_ ```python createInterconnectedPopulations( - num_populations, id_prefix, setup_name, + num_populations, id_prefix, setup_id, host_migration_rate=0, vector_migration_rate=0, host_host_contact_rate=0, host_vector_contact_rate=0, vector_host_contact_rate=0, @@ -1667,7 +1667,7 @@ _Arguments:_ - num_populations -- number of populations to be created (int) - id_prefix -- prefix for IDs to be used for this population in the model, (String) -- setup_name -- setup object with parameters for all populations (Setup) +- setup_id -- setup object with parameters for all populations (Setup) _Keyword arguments:_ - host_migration_rate -- host migration rate between populations; diff --git a/changelog.md b/changelog.md index d6dc812..aadebd7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,15 @@ # Opqua Changelog +## v1.2.1 +## 1 Feb 2024 +Fixed some bugs in function importing (or lack thereof; recommended practice isĀ  +to declare function parameters as Python code in same scope as simulation and +pass function names as arguments to `newSetup()` or `loadSetup()`). + +Also corrected small bug in which `importlib_resources` wasn't being imported in +`Setup` class. + ## v1.2.0 ## 1 Feb 2024 Woke up and decided yesterday's changes were significant enough to warrant a diff --git a/opqua/internal/setup.py b/opqua/internal/setup.py index 9fed01d..7b9e7e3 100644 --- a/opqua/internal/setup.py +++ b/opqua/internal/setup.py @@ -2,9 +2,10 @@ """Contains class Setup.""" # import importlib.util -# import importlib_resources +import importlib_resources import sys import io +import ast import pandas as pd class Setup(object): @@ -65,6 +66,13 @@ def setParameters(self,**kwargs): setattr( self, parameter, value ) self.num_loci = int(self.num_loci) + if self.population_threshold <= 0 and self.selection_threshold <= 0: + self.population_threshold = 0 + self.selection_threshold = 100 # arbitrarily large + if self.population_threshold <= 0: + self.population_threshold = 1 / self.selection_threshold + elif self.selection_threshold <= 0: + self.selection_threshold = 1 / self.population_threshold if isinstance(self.possible_alleles, list): self.possible_alleles = self.possible_alleles @@ -89,6 +97,10 @@ def save(self,save_to_file): # checks if parameter is function out = out + parameter + ',#FUNCTION:'+parameter+'Function\n' function_counter += 1 + elif isinstance( getattr(self,parameter), list ): + out = out + parameter + ',#LIST:' + str( + getattr(self,parameter) + )+'\n' else: out = out + parameter + ',' + str( getattr(self,parameter) )+'\n' @@ -114,6 +126,9 @@ def load(self,file,preset=None,**kwargs): (String, default None) **kwargs -- setup parameters and values """ + self.population_threshold = -1 + self.selection_threshold = -1 + if preset is None: df = pd.read_csv(file) else: @@ -133,15 +148,21 @@ def load(self,file,preset=None,**kwargs): # spec.loader.exec_module(function_params) for i,row in df.iterrows(): - if '#FUNCTION:' in str(row['Value']): # checks if parameter is function + if '#FUNCTION:' in str( row['Value'] ): + # checks if parameter is function function_name = row['Value'][len('#FUNCTION'):].strip() - if ( #function_file_path is None or - not hasattr(function_params, function_name) ): + # if ( function_file_path is None or + # not hasattr(function_params, function_name) ): + if len( str( row['Value'] ).strip() ) == len('#FUNCTION:'): setattr( self, row['Parameter'], lambda g:1 ) else: setattr( self, row['Parameter'], getattr( function_params, function_name ) ) + elif '#LIST:' in str(row['Value']): + setattr( self, row['Parameter'], ast.literal_eval( + row['Value'][ len('#LIST:'): ] + ) ) elif not isinstance(row['Value'], str) and pd.isna(row['Value']): setattr( self, row['Parameter'], None ) elif isinstance(row['Value'], str) and ( diff --git a/opqua/model.py b/opqua/model.py index 423ac87..4a5ccaa 100644 --- a/opqua/model.py +++ b/opqua/model.py @@ -215,24 +215,24 @@ def newSetup(self, name, preset=None, **kwargs): elif len(kwargs) > 0: self.setups[name].setParameters(**kwargs) - def saveSetup(self, setup_name, save_to_file): + def saveSetup(self, setup_id, save_to_file): """ Saves Setup parameters to given file location as a CSV file. Functions (e.g. fitness functions) cannot be saved in this format. Arguments: - setup_name -- name of setup used as a key in setups dictionary + setup_id -- name of setup used as a key in setups dictionary save_to_file -- file path and name to save parameters under (String) """ - self.setups[setup_name].save(save_to_file) + self.setups[setup_id].save(save_to_file) - def loadSetup(self, setup_name, file, preset=None, **kwargs): + def loadSetup(self, setup_id, file, preset=None, **kwargs): """ Loads Setup parameters from CSV file at given location. Arguments: - setup_name -- name of setup to be used as a key in setups dictionary + setup_id -- name of setup to be used as a key in setups dictionary file -- file path to CSV file with parameters (String) Keyword arguments: @@ -240,10 +240,10 @@ def loadSetup(self, setup_name, file, preset=None, **kwargs): (String, default None) **kwargs -- setup parameters and values """ - if setup_name not in self.setups.keys(): - self.setups[setup_name] = Setup() + if setup_id not in self.setups.keys(): + self.setups[setup_id] = Setup() - self.setups[setup_name].load(file, preset=preset, **kwargs) + self.setups[setup_id].load(file, preset=preset, **kwargs) def newIntervention(self, time, method_name, args): """Create a new intervention to be carried out at a specific time. @@ -1116,14 +1116,14 @@ def getCompositionData( ### Model interventions: ### - def newPopulation(self, id, setup_name, num_hosts=0, num_vectors=0): + def newPopulation(self, id, setup_id, num_hosts=0, num_vectors=0): """Create a new Population object with setup parameters. If population ID is already in use, appends _2 to it Arguments: id -- unique identifier for this population in the model (String) - setup_name -- setup object with parameters for this population (Setup) + setup_id -- setup object with parameters for this population (Setup) Keyword arguments: num_hosts -- number of hosts to initialize population with (default 100; @@ -1136,7 +1136,7 @@ def newPopulation(self, id, setup_name, num_hosts=0, num_vectors=0): id = id+'_2' self.populations[id] = Population( - self, id, self.setups[setup_name], num_hosts, num_vectors + self, id, self.setups[setup_id], num_hosts, num_vectors ) for p in self.populations: @@ -1251,7 +1251,7 @@ def linkPopulationsVectorHostContact(self, pop1_id, pop2_id, rate): ) def createInterconnectedPopulations( - self, num_populations, id_prefix, setup_name, + self, num_populations, id_prefix, setup_id, host_migration_rate=0, vector_migration_rate=0, host_host_contact_rate=0, host_vector_contact_rate=0, vector_host_contact_rate=0, @@ -1267,7 +1267,7 @@ def createInterconnectedPopulations( num_populations -- number of populations to be created (int) id_prefix -- prefix for IDs to be used for this population in the model, (String) - setup_name -- setup object with parameters for all populations (Setup) + setup_id -- setup object with parameters for all populations (Setup) Keyword arguments: host_migration_rate -- host migration rate between populations; @@ -1288,7 +1288,7 @@ def createInterconnectedPopulations( new_pops = [ Population( - self, str(id_prefix) + str(i), self.setups[setup_name], + self, str(id_prefix) + str(i), self.setups[setup_id], num_hosts, num_vectors ) for i in range(num_populations) ] diff --git a/opqua/parameters/host-host.csv b/opqua/parameters/host-host.csv index 41a51b5..98eaf23 100644 --- a/opqua/parameters/host-host.csv +++ b/opqua/parameters/host-host.csv @@ -2,28 +2,28 @@ Parameter,Value id,my_setup num_loci,10 possible_alleles,ATCG -fitnessHost,#FUNCTION:fitnessHostFunction -contactHost,#FUNCTION:contactHostFunction -receiveContactHost,#FUNCTION:receiveContactHostFunction -mortalityHost,#FUNCTION:mortalityHostFunction -natalityHost,#FUNCTION:natalityHostFunction -recoveryHost,#FUNCTION:recoveryHostFunction -migrationHost,#FUNCTION:migrationHostFunction -populationContactHost,#FUNCTION:populationContactHostFunction -receivePopulationContactHost,#FUNCTION:receivePopulationContactHostFunction -mutationHost,#FUNCTION:mutationHostFunction -recombinationHost,#FUNCTION:recombinationHostFunction -fitnessVector,#FUNCTION:fitnessVectorFunction -contactVector,#FUNCTION:contactVectorFunction -receiveContactVector,#FUNCTION:receiveContactVectorFunction -mortalityVector,#FUNCTION:mortalityVectorFunction -natalityVector,#FUNCTION:natalityVectorFunction -recoveryVector,#FUNCTION:recoveryVectorFunction -migrationVector,#FUNCTION:migrationVectorFunction -populationContactVector,#FUNCTION:populationContactVectorFunction -receivePopulationContactVector,#FUNCTION:receivePopulationContactVectorFunction -mutationVector,#FUNCTION:mutationVectorFunction -recombinationVector,#FUNCTION:recombinationVectorFunction +fitnessHost,#FUNCTION: +contactHost,#FUNCTION: +receiveContactHost,#FUNCTION: +mortalityHost,#FUNCTION: +natalityHost,#FUNCTION: +recoveryHost,#FUNCTION: +migrationHost,#FUNCTION: +populationContactHost,#FUNCTION: +receivePopulationContactHost,#FUNCTION: +mutationHost,#FUNCTION: +recombinationHost,#FUNCTION: +fitnessVector,#FUNCTION: +contactVector,#FUNCTION: +receiveContactVector,#FUNCTION: +mortalityVector,#FUNCTION: +natalityVector,#FUNCTION: +recoveryVector,#FUNCTION: +migrationVector,#FUNCTION: +populationContactVector,#FUNCTION: +receivePopulationContactVector,#FUNCTION: +mutationVector,#FUNCTION: +recombinationVector,#FUNCTION: contact_rate_host_vector,0 transmission_efficiency_host_vector,0 transmission_efficiency_vector_host,0 diff --git a/opqua/parameters/vector-borne.csv b/opqua/parameters/vector-borne.csv index ff34045..6a8ab73 100644 --- a/opqua/parameters/vector-borne.csv +++ b/opqua/parameters/vector-borne.csv @@ -2,28 +2,28 @@ Parameter,Value id,my_setup num_loci,10 possible_alleles,ATCG -fitnessHost,#FUNCTION:fitnessHostFunction -contactHost,#FUNCTION:contactHostFunction -receiveContactHost,#FUNCTION:receiveContactHostFunction -mortalityHost,#FUNCTION:mortalityHostFunction -natalityHost,#FUNCTION:natalityHostFunction -recoveryHost,#FUNCTION:recoveryHostFunction -migrationHost,#FUNCTION:migrationHostFunction -populationContactHost,#FUNCTION:populationContactHostFunction -receivePopulationContactHost,#FUNCTION:receivePopulationContactHostFunction -mutationHost,#FUNCTION:mutationHostFunction -recombinationHost,#FUNCTION:recombinationHostFunction -fitnessVector,#FUNCTION:fitnessVectorFunction -contactVector,#FUNCTION:contactVectorFunction -receiveContactVector,#FUNCTION:receiveContactVectorFunction -mortalityVector,#FUNCTION:mortalityVectorFunction -natalityVector,#FUNCTION:natalityVectorFunction -recoveryVector,#FUNCTION:recoveryVectorFunction -migrationVector,#FUNCTION:migrationVectorFunction -populationContactVector,#FUNCTION:populationContactVectorFunction -receivePopulationContactVector,#FUNCTION:receivePopulationContactVectorFunction -mutationVector,#FUNCTION:mutationVectorFunction -recombinationVector,#FUNCTION:recombinationVectorFunction +fitnessHost,#FUNCTION: +contactHost,#FUNCTION: +receiveContactHost,#FUNCTION: +mortalityHost,#FUNCTION: +natalityHost,#FUNCTION: +recoveryHost,#FUNCTION: +migrationHost,#FUNCTION: +populationContactHost,#FUNCTION: +receivePopulationContactHost,#FUNCTION: +mutationHost,#FUNCTION: +recombinationHost,#FUNCTION: +fitnessVector,#FUNCTION: +contactVector,#FUNCTION: +receiveContactVector,#FUNCTION: +mortalityVector,#FUNCTION: +natalityVector,#FUNCTION: +recoveryVector,#FUNCTION: +migrationVector,#FUNCTION: +populationContactVector,#FUNCTION: +receivePopulationContactVector,#FUNCTION: +mutationVector,#FUNCTION: +recombinationVector,#FUNCTION: contact_rate_host_vector,0.2 transmission_efficiency_host_vector,1 transmission_efficiency_vector_host,1 diff --git a/setup.py b/setup.py index b65ed25..d24b80d 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='opqua', - version='v1.2.0', + version='v1.2.1', description='An epidemiological modeling framework for population ' \ + 'genetics and evolution.', long_description='Opqua is an epidemiological modeling framework for ' \ @@ -22,7 +22,7 @@ + 'github.com/pablocarderam/opqua for more information.', description_file='README.md', url='https://github.com/pablocarderam/opqua', - download_url='https://github.com/pablocarderam/opqua/archive/v1.2.0.tar.gz', + download_url='https://github.com/pablocarderam/opqua/archive/v1.2.1.tar.gz', author='Pablo Cardenas', author_email='pablocarderam@gmail.com', keywords=['epidemiology','evolution','biology'],