├── data └── rapa_nui │ ├── ahu.cpg │ ├── ahu.dbf │ ├── ahu.shp │ ├── ahu.shx │ ├── ahu.prj │ └── ahu.qpj ├── networkdrift ├── __init__.py ├── utils │ ├── __init__.py │ └── utils.py └── demography │ ├── __init__.py │ └── network.py ├── required-modules.txt ├── .idea └── other.xml ├── setup.py ├── simuPOP_examples ├── islandMigration.py ├── islandMigration-FromCookbook.py ├── geneticDrift.py ├── simuOptParam2argparse.py ├── simuOptToParamsConversion.py └── simuMigration.py ├── testdata ├── test_graph.gml ├── island_test.gml └── test-network.gml ├── README.md └── models ├── shapefile_to_gml.py ├── parameter-sweep-for-localization.py ├── network-subpop-eval.py └── network-k-eval.py /data/rapa_nui/ahu.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /networkdrift/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /networkdrift/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /networkdrift/demography/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/rapa_nui/ahu.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nevrome/network-drift/master/data/rapa_nui/ahu.dbf -------------------------------------------------------------------------------- /data/rapa_nui/ahu.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nevrome/network-drift/master/data/rapa_nui/ahu.shp -------------------------------------------------------------------------------- /data/rapa_nui/ahu.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nevrome/network-drift/master/data/rapa_nui/ahu.shx -------------------------------------------------------------------------------- /required-modules.txt: -------------------------------------------------------------------------------- 1 | datetime 2 | matplotlib 3 | operator 4 | simuPOP 5 | argparse 6 | itertools 7 | logging 8 | math 9 | numpy 10 | random 11 | sys 12 | uuid 13 | scipy 14 | time 15 | seaborn 16 | networkx 17 | -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup(name='network_drift', 6 | version='1.0', 7 | description='Python Distribution Utilities', 8 | packages=['networkdrift.demography', 'networkdrift.utils'], 9 | ) 10 | -------------------------------------------------------------------------------- /data/rapa_nui/ahu.prj: -------------------------------------------------------------------------------- 1 | PROJCS["WGS_1984_UTM_Zone_12S",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-111],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",10000000],UNIT["Meter",1]] -------------------------------------------------------------------------------- /data/rapa_nui/ahu.qpj: -------------------------------------------------------------------------------- 1 | PROJCS["WGS 84 / UTM zone 12S",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-111],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",10000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32712"]] 2 | -------------------------------------------------------------------------------- /simuPOP_examples/islandMigration.py: -------------------------------------------------------------------------------- 1 | 2 | import simuPOP as sim 3 | from simuPOP.utils import migrIslandRates 4 | 5 | p = [0.2, 0.3, 0.5] 6 | 7 | pop = sim.Population(size=[10000]*3, loci=1, infoFields='migrate_to') 8 | simu = sim.Simulator(pop, rep=2) 9 | 10 | simu.evolve( 11 | initOps=[sim.InitSex()] + 12 | [sim.InitGenotype(prop=[p[i], 1-p[i]], subPops=i) for i in range(3)], 13 | 14 | preOps=sim.Migrator(rate=migrIslandRates(0.01, 3), reps=0), 15 | matingScheme=sim.RandomMating(), 16 | postOps=[ 17 | sim.Stat(alleleFreq=0, structure=0, vars='alleleFreq_sp', step=50), 18 | sim.PyEval(r"'Fst=%.3f (%s)' % (F_st, ', '.join(['%.2f’ % " 19 | "subPop[x]['alleleFreq'][0][0] for x in range(3)]))", 20 | step=50), 21 | sim.PyOutput('\n', reps=-1, step=50), 22 | ], 23 | gen=201 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /testdata/test_graph.gml: -------------------------------------------------------------------------------- 1 | graph [ 2 | name "island_network" 3 | node [ 4 | id 0 5 | label "island_0" 6 | x_coord "30" 7 | y_coord "10" 8 | ] 9 | node [ 10 | id 1 11 | label "island_1" 12 | x_coord "20" 13 | y_coord "30" 14 | ] 15 | node [ 16 | id 2 17 | label "island_2" 18 | x_coord "10" 19 | y_coord "10" 20 | ] 21 | node [ 22 | id 3 23 | label "island_3" 24 | x_coord "20" 25 | y_coord "0" 26 | ] 27 | node [ 28 | id 4 29 | label "island_4" 30 | x_coord "10" 31 | y_coord "20" 32 | ] 33 | edge [ 34 | id 0 35 | source 0 36 | target 1 37 | value 1.0 38 | ] 39 | edge [ 40 | id 1 41 | source 1 42 | target 2 43 | value 0.5 44 | ] 45 | edge [ 46 | id 2 47 | source 2 48 | target 3 49 | value 0.5 50 | ] 51 | edge [ 52 | id 3 53 | source 4 54 | target 0 55 | value 1.0 56 | ] 57 | edge [ 58 | id 4 59 | source 4 60 | target 3 61 | value 0.5 62 | ] 63 | edge 64 | [ id 5 65 | source 3 66 | target 1 67 | value 1.0 68 | ] 69 | edge 70 | [ id 6 71 | source 2 72 | target 4 73 | value 0.5 74 | ] 75 | edge [ 76 | id 7 77 | source 1 78 | target 4 79 | value 1.0 80 | ] 81 | ] 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Network-drift

2 | 3 | Cultural transmission models in python using simuPOP, SciPy, and NumPy. 4 | 5 | network-drift is a python-based software layer on top of simuPOP by Bo Peng, currently version 1.9. See http://simupop.sourceforge.net/ for source code, license, and documentation. 6 | 7 |

Directory Structure

8 | 9 | demography : python modules for the population structure. Currently, this consists just of network configuration. Networks can be configured as GML files or by specifiying numbers of nodes and connectivity (producing small-world graphs). 10 | 11 | models : python script to run the simulation models. 12 | 13 | simuPOP_examples : python scripts that demonstrate features of simuPOP from documentation and cookbooks. 14 | 15 | testdata : sample GML files 16 | 17 |

Module Dependencies

18 | 19 | find . -name "*.py" | xargs grep -h 'import ' | grep -v simu | sort | cut -d' ' -f2 | uniq > required-modules.txt 20 | 21 |

Author

22 | 23 | Carl P. Lipo and Mark E. Madsen Copyright 2018-2019. All rights reserved. This software is made available under the Apache Software License (see file LICENSE), which allows you to use the software for commercial or non-commercial purposes, but you must attribute authorship, and derivatives must allow the user to find the original code and license. 24 | -------------------------------------------------------------------------------- /simuPOP_examples/islandMigration-FromCookbook.py: -------------------------------------------------------------------------------- 1 | # this example is from Antao's 2015 - Bioinformation with Python Cookbook 2 | # Chapter 5 - Simulating population structure using island and stepping-stone models 3 | # page 138 4 | 5 | from collections import defaultdict, OrderedDict 6 | from copy import deepcopy 7 | import simuPOP as sp 8 | from simuPOP import demography 9 | num_loci = 10 10 | pop_size = 50 11 | num_gens = 101 12 | num_pops = 10 13 | migs = [0, 0.005, 0.01, 0.02, 0.05, 0.1] 14 | init_ops = OrderedDict() 15 | pre_ops = OrderedDict() 16 | post_ops = OrderedDict() 17 | pops = sp.Population([pop_size] * num_pops, loci=[1] * 18 | num_loci, infoFields=['migrate_to']) 19 | 20 | 21 | def init_accumulators(pop, param): 22 | accumulators = param 23 | for accumulator in accumulators: 24 | if accumulator.endswith('_sp'): 25 | pop.vars()[accumulator] = defaultdict(list) 26 | else: 27 | pop.vars()[accumulator] = [] 28 | return True 29 | 30 | 31 | def update_accumulator(pop, param): 32 | accumulator, var = param 33 | if var.endswith('_sp'): 34 | for sp in range(pop.numSubPop()): 35 | pop.vars()[accumulator][sp].append( 36 | deepcopy(pop.vars(sp)[var[:-3]])) 37 | else: 38 | pop.vars()[accumulator].append(deepcopy(pop.vars()[var])) 39 | return True 40 | 41 | init_ops['accumulators'] = sp.PyOperator(init_accumulators, 42 | param=['fst']) 43 | init_ops['Sex'] = sp.InitSex() 44 | init_ops['Freq'] = sp.InitGenotype(freq=[0.5, 0.5]) 45 | for i, mig in enumerate(migs): 46 | post_ops['mig-%d' % i] = sp.Migrator(demography.migrIslandRates(mig, num_pops), 47 | reps=[i]) 48 | post_ops['Stat-fst'] = sp.Stat(structure=sp.ALL_AVAIL) 49 | post_ops['fst_accumulation'] = sp.PyOperator(update_accumulator, param=('fst', 'F_st')) 50 | 51 | mating_scheme = sp.RandomMating() 52 | 53 | sim = sp.Simulator(pops, rep=len(migs)) 54 | sim.evolve(initOps=list(init_ops.values()), 55 | preOps=list(pre_ops.values()), 56 | postOps=list(post_ops.values()), 57 | matingScheme=mating_scheme, 58 | gen=num_gens) 59 | 60 | import seaborn as sns 61 | sns.set_style('white') 62 | import matplotlib.pyplot as plt 63 | fig = plt.figure(figsize=(16, 9)) 64 | ax = fig.add_subplot(111) 65 | for i, pop in enumerate(sim.populations()): 66 | ax.plot(pop.dvars().fst, label='mig rate %.4f' % migs[i]) 67 | ax.legend(loc=2) 68 | ax.set_ylabel('FST') 69 | ax.set_xlabel('Generation') 70 | -------------------------------------------------------------------------------- /simuPOP_examples/geneticDrift.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | This program demonstrates changes of allele frequency on single locus due to genetic drift. 4 | """ 5 | 6 | import simuOpt, os, sys, time 7 | from simuPOP import * 8 | import argparse 9 | import rapanuisim.utils as utils 10 | 11 | try: 12 | from simuPOP.plotter import VarPlotter 13 | except: 14 | print("simuRPy import failed. Please check your rpy installation.") 15 | print("Allele Frequencies will not be plotted") 16 | useRPy = False 17 | else: 18 | useRPy = True 19 | 20 | def simuGeneticDrift(popSize=100, p=0.2, generations=100, replications=5): 21 | '''Simulate the Genetic Drift as a result of random mating.''' 22 | # diploid population, one chromosome with 1 locus 23 | # random mating with sex 24 | pop = Population(size=popSize, loci=args.numloci) 25 | 26 | initial_distribution = utils.constructUniformAllelicDistribution(args.numloci) 27 | 28 | simu = Simulator(pop, rep=replications) 29 | 30 | if useRPy: 31 | plotter = VarPlotter('alleleFreq[0][0]', ylim=[0, 1], ylab='allele frequency', 32 | update=generations - 1, saveAs='geneticDrift.png') 33 | else: 34 | plotter = NoneOp() 35 | 36 | # if number of generation is smaller than 200, step is 10 generations, 37 | # if it's between 200 and 500, set step to be 20 generations, 38 | # otherwise, step = 50 generations. 39 | if generations <= 200: 40 | s = 10 41 | elif 200 < generations <= 500: 42 | s = 20 43 | else: 44 | s = 50 45 | 46 | simu.evolve( 47 | # everyone initially will have the same allele frequency 48 | initOps=[ 49 | InitSex(), 50 | InitGenotype(freq=[p, 1 - p]) 51 | ], 52 | matingScheme=RandomMating(), 53 | postOps=[ 54 | Stat(alleleFreq=[0]), 55 | PyEval(r'"Generation %d:\t" % gen', reps=0, step=s), 56 | PyEval(r"'%.3f\t' % alleleFreq[0][0]", step=s), 57 | PyOutput('\n', reps=-1, step=s), 58 | plotter, 59 | ], 60 | gen=generations 61 | ) 62 | 63 | 64 | if __name__ == '__main__': 65 | # get all parameters 66 | args = argparse.ArgumentParser() 67 | args.add_argument("--popSize", 68 | default=100, 69 | type=int, 70 | help="Population Size") 71 | args.add_argument("--p", 72 | default=0.05, 73 | type=float, 74 | help="Initial Allele Frequency") 75 | args.add_argument("--generations", 76 | default=100, 77 | type=int, 78 | help="Number of Generations") 79 | args.add_argument("--replications", 80 | default=8, 81 | type=int, 82 | help="Number of Replicates") 83 | args.add_argument("--numloci", 84 | default=30, 85 | type=int, 86 | help="Number of loci to model (number of features)") 87 | args = args.parse_args() 88 | 89 | simuGeneticDrift(args.popSize, args.p, args.generations, args.replications) 90 | 91 | # wait ten seconds before exit 92 | if useRPy: 93 | print("Figure will be closed after 5 seconds.") 94 | time.sleep(5) 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /testdata/island_test.gml: -------------------------------------------------------------------------------- 1 | graph [ 2 | name "island_network" 3 | node [ 4 | id 0 5 | label "0" 6 | SUID "0" 7 | sharedname "island_0" 8 | name "island_4" 9 | selected "false" 10 | x_coord "30" 11 | y_coord "10" 12 | ] 13 | node [ 14 | id 1 15 | label "1" 16 | SUID "2" 17 | sharedname "island_1" 18 | name "island_1" 19 | selected "false" 20 | x_coord "20" 21 | y_coord "30" 22 | ] 23 | node [ 24 | id 2 25 | label "2" 26 | sharedname "island_2" 27 | name "island_2" 28 | selected "false" 29 | x_coord "10" 30 | y_coord "10" 31 | ] 32 | node [ 33 | id 3 34 | label "3" 35 | sharedname "island_3" 36 | name "island_3" 37 | selected "false" 38 | x_coord "20" 39 | y_coord "0" 40 | ] 41 | node [ 42 | id 4 43 | label "4" 44 | SUID "4" 45 | sharedname "island_4" 46 | name "island_4" 47 | selected "false" 48 | x_coord "10" 49 | y_coord "20" 50 | ] 51 | edge [ 52 | id 0 53 | source 0 54 | target 1 55 | value 1.0 56 | SUID "104" 57 | sharedname "Node 0 (interaction type) Node 1" 58 | sharedinteraction "interaction type" 59 | name "Node 0 (interaction type) Node 1" 60 | selected "false" 61 | interaction "interaction type" 62 | ] 63 | edge [ 64 | id 1 65 | source 1 66 | target 2 67 | value 0.5 68 | SUID "103" 69 | sharedname "Node 1 (interaction type) Node 2" 70 | sharedinteraction "interaction type" 71 | name "Node 1 (interaction type) Node 2" 72 | selected "false" 73 | interaction "interaction type" 74 | ] 75 | edge [ 76 | id 2 77 | source 2 78 | target 3 79 | value 0.5 80 | SUID "102" 81 | sharedname "Node 2 (interaction type) Node 3" 82 | sharedinteraction "interaction type" 83 | name "Node 2 (interaction type) Node 3" 84 | selected "false" 85 | interaction "interaction type" 86 | ] 87 | edge [ 88 | id 3 89 | source 4 90 | target 0 91 | value 1.0 92 | SUID "101" 93 | sharedname "Node 4 (interaction type) Node 0" 94 | sharedinteraction "interaction type" 95 | name "Node 4 (interaction type) Node 0" 96 | selected "false" 97 | interaction "interaction type" 98 | ] 99 | edge [ 100 | id 4 101 | source 4 102 | target 3 103 | value 0.5 104 | SUID "100" 105 | sharedname "Node 4 (interaction type) Node 3" 106 | sharedinteraction "interaction type" 107 | name "Node 4 (interaction type) Node 3" 108 | selected "false" 109 | interaction "interaction type" 110 | ] 111 | edge 112 | [ id 5 113 | source 3 114 | target 1 115 | value 1.0 116 | SUID "99" 117 | sharedname "Node 3 (interaction type) Node 1" 118 | sharedinteraction "interaction type" 119 | name "Node 3 (interaction type) Node 1" 120 | selected "false" 121 | interaction "interaction type" 122 | ] 123 | edge 124 | [ id 6 125 | source 2 126 | target 4 127 | value 0.5 128 | SUID "98" 129 | sharedname "Node 2 (interaction type) Node 4" 130 | sharedinteraction "interaction type" 131 | name "Node 2 (interaction type) Node 4" 132 | selected "false" 133 | interaction "interaction type" 134 | ] 135 | edge [ 136 | id 7 137 | source 1 138 | target 4 139 | value 1.0 140 | SUID "97" 141 | sharedname "Node 1 (interaction type) Node 4" 142 | sharedinteraction "interaction type" 143 | name "Node 1 (interaction type) Node 4" 144 | selected "true" 145 | interaction "interaction type" 146 | ] 147 | ] 148 | -------------------------------------------------------------------------------- /simuPOP_examples/simuOptParam2argparse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # 5 | # To perform conversion: 6 | # 7 | # python simuOptParam2argparse.py filename start_lineno ending_lineno 8 | # 9 | 10 | import argparse 11 | 12 | 13 | def convert_options(filename, startline, endline, name): 14 | with open(filename) as script: 15 | content = ''.join(script.readlines()[startline: endline]) 16 | prefix = ''' 17 | class FakeSimuOptClass: 18 | def __init__(self): 19 | for name in [ 20 | 'valueNot', 21 | 'valueOr', 22 | 'valueAnd', 23 | 'valueOneOf', 24 | 'valueTrueFalse', 25 | 'valueBetween', 26 | 'valueGT', 27 | 'valueGE', 28 | 'valueLT', 29 | 'valueLE', 30 | 'valueEqual', 31 | 'valueNotEqual', 32 | 'valueIsInteger', 33 | 'valueIsNum', 34 | 'valueIsList', 35 | 'valueValidDir', 36 | 'valueValidFile', 37 | 'valueListOf', 38 | 'valueSumTo']: 39 | setattr(self, name, lambda *args, **kwargs: None) 40 | simuOpt = FakeSimuOptClass() 41 | ''' 42 | # replace list 43 | replaces = [ 44 | ] 45 | 46 | for old, new in replaces: 47 | content = content.replace(old, new) 48 | value = {} 49 | exec(prefix + content, value) 50 | 51 | print('import argparse') 52 | print('args = argparse.ArgumentParser()') 53 | for opt in value[name]: 54 | if 'name' not in opt: 55 | continue 56 | name = opt['name'] 57 | default = "\n default={!r},".format(opt['default']) if 'default' in opt else None 58 | type = opt['type'] if 'type' in opt else None 59 | help = opt['label'] if 'label' in opt else '' 60 | help = (help + '\n ' + opt['description']) if 'description' in opt else help 61 | if 'type' in opt: 62 | if opt['type'] == 'filename': 63 | topt = '' 64 | elif not isinstance(opt['type'], (list, tuple)): 65 | if hasattr(opt['type'], '__name__'): 66 | topt = '\n type={},'.format(opt['type'].__name__) 67 | elif opt['type'] == 'integers': 68 | topt = '\n nargs="*",\n type=int,' 69 | elif opt['type'] == 'numbers': 70 | topt = '\n nargs="*",\n type=float,' 71 | elif opt['type'] == 'integer': 72 | topt = '\n type=int,' 73 | elif opt['type'] == 'number': 74 | topt = '\n type=float,' 75 | elif opt['type'] == 'string': 76 | pass 77 | elif opt['type'] == 'strings': 78 | topt = '\n nargs="*",' 79 | else: 80 | topt = '\n type={},'.format(opt['type']) 81 | else: 82 | topt = '\n nargs="*",' 83 | print('args.add_argument("--{}",{}{}\n help="""{}""")'.format(name, default, topt, help)) 84 | print('args = args.parse_args()') 85 | 86 | 87 | if __name__ == '__main__': 88 | parser = argparse.ArgumentParser('simuOptParam2argparse.py', 89 | description='''This script converts a now deprecated simuOpt.Param 90 | list to argparse definitions. It is intended to be a 91 | semi-automatic process that assist your conversion from 92 | simuOpt.Param to argparser. 93 | ''') 94 | parser.add_argument('filename', help='''file that contains the 95 | simuOpt option list.''') 96 | parser.add_argument('startline', type=int, 97 | help='''starting line of the option list''') 98 | parser.add_argument('endline', type=int, 99 | help='''last line of the option list''') 100 | parser.add_argument('name', 101 | help='''Name of the option list.''') 102 | args = parser.parse_args() 103 | # 104 | convert_options(args.filename, args.startline, args.endline, args.name) -------------------------------------------------------------------------------- /simuPOP_examples/simuOptToParamsConversion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # 5 | # To perform conversion: 6 | # 7 | # python simuOptParam2argparse.py filename start_lineno ending_lineno 8 | # 9 | 10 | import argparse 11 | 12 | 13 | def convert_options(filename, startline, endline, name): 14 | with open(filename) as script: 15 | content = ''.join(script.readlines()[startline: endline]) 16 | prefix = ''' 17 | class FakeSimuOptClass: 18 | def __init__(self): 19 | for name in [ 20 | 'valueNot', 21 | 'valueOr', 22 | 'valueAnd', 23 | 'valueOneOf', 24 | 'valueTrueFalse', 25 | 'valueBetween', 26 | 'valueGT', 27 | 'valueGE', 28 | 'valueLT', 29 | 'valueLE', 30 | 'valueEqual', 31 | 'valueNotEqual', 32 | 'valueIsInteger', 33 | 'valueIsNum', 34 | 'valueIsList', 35 | 'valueValidDir', 36 | 'valueValidFile', 37 | 'valueListOf', 38 | 'valueSumTo']: 39 | setattr(self, name, lambda *args, **kwargs: None) 40 | simuOpt = FakeSimuOptClass() 41 | ''' 42 | # replace list 43 | replaces = [ 44 | ] 45 | 46 | for old, new in replaces: 47 | content = content.replace(old, new) 48 | value = {} 49 | exec(prefix + content, value) 50 | 51 | print('import argparse') 52 | print('args = argparse.ArgumentParser()') 53 | for opt in value[name]: 54 | if 'name' not in opt: 55 | continue 56 | name = opt['name'] 57 | default = "\n default={!r},".format(opt['default']) if 'default' in opt else None 58 | type = opt['type'] if 'type' in opt else None 59 | help = opt['label'] if 'label' in opt else '' 60 | help = (help + '\n ' + opt['description']) if 'description' in opt else help 61 | if 'type' in opt: 62 | if opt['type'] == 'filename': 63 | topt = '' 64 | elif not isinstance(opt['type'], (list, tuple)): 65 | if hasattr(opt['type'], '__name__'): 66 | topt = '\n type={},'.format(opt['type'].__name__) 67 | elif opt['type'] == 'integers': 68 | topt = '\n nargs="*",\n type=int,' 69 | elif opt['type'] == 'numbers': 70 | topt = '\n nargs="*",\n type=float,' 71 | elif opt['type'] == 'integer': 72 | topt = '\n type=int,' 73 | elif opt['type'] == 'number': 74 | topt = '\n type=float,' 75 | elif opt['type'] == 'string': 76 | pass 77 | elif opt['type'] == 'strings': 78 | topt = '\n nargs="*",' 79 | else: 80 | topt = '\n type={},'.format(opt['type']) 81 | else: 82 | topt = '\n nargs="*",' 83 | print('args.add_argument("--{}",{}{}\n help="""{}""")'.format(name, default, topt, help)) 84 | print('args = args.parse_args()') 85 | 86 | 87 | if __name__ == '__main__': 88 | parser = argparse.ArgumentParser('simuOptParam2argparse.py', 89 | description='''This script converts a now deprecated simuOpt.Param 90 | list to argparse definitions. It is intended to be a 91 | semi-automatic process that assist your conversion from 92 | simuOpt.Param to argparser. 93 | ''') 94 | parser.add_argument('filename', help='''file that contains the 95 | simuOpt option list.''') 96 | parser.add_argument('startline', type=int, 97 | help='''starting line of the option list''') 98 | parser.add_argument('endline', type=int, 99 | help='''last line of the option list''') 100 | parser.add_argument('name', 101 | help='''Name of the option list.''') 102 | args = parser.parse_args() 103 | # 104 | convert_options(args.filename, args.startline, args.endline, args.name) -------------------------------------------------------------------------------- /simuPOP_examples/simuMigration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Demonstrate the changes of allele frequencies among subpopulations due to migration 4 | # 5 | 6 | """ 7 | This program demonstrates the effect of migration by rate which results in 8 | changes of allele frequencies among subpopulations. 9 | """ 10 | 11 | import simuOpt as sim 12 | import os, sys, types, time 13 | from simuPOP import * 14 | from simuOpt import * 15 | import argparse 16 | 17 | try: 18 | from simuPOP.plotter import VarPlotter 19 | except: 20 | print("simuRPy import failed. Please check your rpy installation.") 21 | print("allele frquencies in subpopulations will not be plotted") 22 | useRPy = False 23 | else: 24 | useRPy = True 25 | 26 | def simuMigration(subPopSize, numOfSubPops, m, generations, numLoci): 27 | '''Simulate the change of allele frequencies among subpopulations as a result of migration.''' 28 | # diploid population, one chromosome with 1 locus 29 | # random mating with sex 30 | pop = Population(size=[subPopSize] * numOfSubPops, loci=numLoci, infoFields=['migrate_to'], ancGen=-1, ploidy=1 ) 31 | # set initial allele frequencies to each subpopulation 32 | # Rule of initialization is to use a list of numbers, which have counts equal to number of 33 | # subpopulations and been set evenly distributed between 0 and 1. 34 | # Therefore, the average allele frequency named theoretical value among all subpopulations will be 0.5 35 | for i in range(numOfSubPops): 36 | initGenotype(pop, freq=[i * 1. / (numOfSubPops - 1), 1 - i * 1. / (numOfSubPops - 1)], subPops=[i]) 37 | 38 | # check if plot 39 | if useRPy: 40 | plotter = VarPlotter('[0.5] + [subPop[i]["alleleFreq"][0][0] for i in range(%d)]' % numOfSubPops, 41 | ylim=[0, 1], update=generations - 1, 42 | legend=['Theoretical'] + ['Allele freq at subpop %d' % x for x in range(numOfSubPops)], 43 | xlab="generation", ylab="alleleFrequency", saveAs='migration.png') 44 | else: 45 | plotter = NoneOp() 46 | 47 | a = [] 48 | # set migration rate matrix a[]. Within each row i of matrix a[], all elements have the same value which is 49 | # m divided by number of subpopulations minus one, except the diagonal element, whose value is set to be 0. 50 | # This setting ensures that every individual in subpopulation i has probability m to become the Migrator, 51 | # and such a migrator.has equal possibility of entering any other subpopulation. 52 | for i in range(numOfSubPops): 53 | b = []; 54 | for j in range(numOfSubPops): 55 | if j == i: 56 | b.append(0) 57 | else: 58 | b.append(m / (numOfSubPops - 1)) 59 | a.append(b) 60 | # if number of generations is smaller than 200, step will be set to 10 generations, otherwise it will be 20. 61 | if generations <= 200: 62 | s = 10 63 | else: 64 | s = 20 65 | pop.evolve( 66 | initOps=InitSex(), 67 | preOps=[ 68 | Stat(alleleFreq=[0], vars=['alleleFreq_sp']), 69 | PyEval(r'"Frequency at generation %d: %s\n" % (gen, ", ".join(["%.2f" % x for x in freq]))', 70 | stmts='freq = [subPop[x]["alleleFreq"][0][0] for x in range(%i)]' % numOfSubPops, step=s), 71 | 72 | ], 73 | matingScheme=RandomMating(ops=CloneGenoTransmitter()), 74 | postOps=[ 75 | KAlleleMutator(k=1, rates=0.01), 76 | Migrator(rate=a), 77 | plotter, 78 | ], 79 | gen=generations, 80 | ) 81 | 82 | 83 | if __name__ == '__main__': 84 | 85 | args = argparse.ArgumentParser() 86 | args.add_argument("--subPopSize", 87 | default=5000, 88 | type=int, 89 | help="SubPopulation Size") 90 | args.add_argument("--numOfSubPops", 91 | default=20, 92 | type=int, 93 | help="Number of Sub Populations") 94 | args.add_argument("--m", 95 | default=0.05, 96 | type=float, 97 | help="Migration Rate") 98 | args.add_argument("--generations", 99 | default=1000, 100 | type=int, 101 | help="Generations") 102 | args.add_argument("--numLoci", 103 | default=5, 104 | type=int, 105 | help="Number of Loci") 106 | args = args.parse_args() 107 | 108 | simuMigration(args.subPopSize, args.numOfSubPops, args.m, args.generations, args.numLoci) 109 | # wait five seconds before exit 110 | if useRPy: 111 | print("Figure will be closed after five seconds.") 112 | time.sleep(5) -------------------------------------------------------------------------------- /models/shapefile_to_gml.py: -------------------------------------------------------------------------------- 1 | import gdal 2 | import networkx as nx 3 | import matplotlib.pyplot as plt 4 | from collections import defaultdict 5 | from itertools import permutations 6 | import operator 7 | import math 8 | import statistics 9 | import argparse 10 | 11 | global config 12 | 13 | ''' 14 | Example use of script. 15 | python3 ./shapefile_to_gml.py 16 | --shapefile ../data/rapa_nui/ahu.shp 17 | --migrationfraction 0.0001 18 | --connectedness 5 19 | --nodecolor blue 20 | --output ahu 21 | 22 | ''' 23 | 24 | def print_graph(network,pos, nodecolor): 25 | """ 26 | Show us the graph of the network. 27 | :return: nothing - should be a matplotlib plot 28 | """ 29 | plt.subplot(111) 30 | nx.draw(network, pos, 31 | node_color=nodecolor, 32 | node_size=30, 33 | width=0.1, 34 | with_labels=False, font_weight='bold') 35 | plt.show() 36 | return True 37 | 38 | def save_graph_plot(network,pos,outputname,connectedness,nodecolor): 39 | """ 40 | Save the graph of the network. 41 | :return: nothing - should be saved file 42 | """ 43 | name = "%s-%s.svg" % (outputname, connectedness) 44 | nx.draw(network, pos, 45 | node_color=nodecolor, 46 | node_size=30, 47 | width=0.1, 48 | with_labels=False, font_weight='bold') 49 | plt.savefig(name) 50 | return True 51 | 52 | def save_gml(network,outputname,nodecolor): 53 | """ 54 | Save the GML file 55 | :return: nothing - should be saved file 56 | """ 57 | name = "%s.gml" % outputname 58 | nx.write_gml(network, name) 59 | return True 60 | 61 | def main(): 62 | parser = argparse.ArgumentParser() 63 | parser.add_argument("--debug", help="turn on debugging output") 64 | parser.add_argument("--shapefile", help="name of shapefile to import", required=True) 65 | parser.add_argument("--migrationfraction", help="base migration fraction", type=float, required=True, default=0.001) 66 | parser.add_argument("--connectedness", help="k value used to connect nodes in network", type=int, required=True, default=5) 67 | parser.add_argument("--output", help="name of output gml", required=True) 68 | parser.add_argument("--nodecolor", help="color of nodes", required=True, default="blue") 69 | 70 | config = parser.parse_args() 71 | 72 | ## the mean nearest neighbor distance will be used to scale the migration fraction - using the distance between nodes 73 | migration_fraction =config.migrationfraction 74 | networkfile = config.shapefile 75 | output = config.output 76 | nodecolor=config.nodecolor 77 | 78 | k=config.connectedness 79 | 80 | network = nx.read_shp(networkfile) 81 | number=0 82 | node_locations={} 83 | node_name={} 84 | 85 | ## first build a list of locations and names for the new nodes 86 | for ((x,y), data) in network.nodes(data=True): 87 | node_locations[number]=(x,y) 88 | node_name[number]=data['NAME'] 89 | number += 1 90 | 91 | nearest_neighbor_distance=[] 92 | new_network=nx.Graph() 93 | for num,xy in list(node_locations.items()): 94 | new_network.add_node(num, x=xy[0], y=xy[1], pos=(xy[0],xy[1]), name=node_name[num]) 95 | pos=nx.get_node_attributes(new_network,'pos') 96 | 97 | for num,xy in list(node_locations.items()): 98 | 99 | x1,y1=xy 100 | #print("working on node %s" % num) 101 | node_distances = [] 102 | sorted_node_distances = [] 103 | # now iterate through to find the n closes networks 104 | ccount=0 105 | for (num2,xy2) in list(node_locations.items()): 106 | x2,y2=xy2 107 | if num != num2: 108 | distance=math.sqrt((x1-x2)**2 + (y1-y2)**2) 109 | node_distances.append(distance) 110 | 111 | sorted_node_distances.append(min(node_distances)) 112 | smallest_distance=min(sorted_node_distances) 113 | nearest_neighbor_distance.append(smallest_distance) 114 | 115 | mean_nearest_neighbor_distance=statistics.mean(nearest_neighbor_distance) 116 | 117 | for num, xy in list(node_locations.items()): 118 | # print("working on node %s" % num) 119 | node_distances = {} 120 | # now iterate through to find the n closes networks 121 | ccount = 0 122 | for (num2, xy2) in list(node_locations.items()): 123 | if num != num2: 124 | node_distances[num2] = (math.sqrt((xy[0] - xy2[0]) ** 2 + (xy[1] - xy2[1]) ** 2)) 125 | sorted_node_distances = sorted(node_distances.items(), key=operator.itemgetter(1)) 126 | list_of_edges_to_add=list(range(0,k)) 127 | current_k=k 128 | for e in list_of_edges_to_add: 129 | ne, dist = sorted_node_distances[e] 130 | # network.add_edge(node_locations[num],node_locations[ne], weight=dist ) 131 | weight = (dist / mean_nearest_neighbor_distance) * migration_fraction 132 | new_network.add_edge(num, ne, weight=weight) 133 | 134 | print_graph(new_network, pos,nodecolor) 135 | save_graph_plot(new_network,pos,output,k,nodecolor) 136 | save_gml(new_network,output,nodecolor) 137 | 138 | if __name__ == "__main__": 139 | main() 140 | -------------------------------------------------------------------------------- /networkdrift/utils/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from collections import defaultdict, OrderedDict 3 | from copy import deepcopy 4 | import simuPOP as sp 5 | import logging as log 6 | import numpy as np 7 | import scipy.stats 8 | import sys 9 | import math 10 | from simuPOP import demography 11 | import os 12 | from collections import Counter 13 | 14 | ### some functions to store stats at each timestep. 15 | 16 | def init_count_traits_in_subpops(pop): 17 | ''' 18 | Count traits in the overall population - zero out the dictionary for each loci/allele 19 | combination in the loci/allele trait space 20 | :param pop: the population object - this is passed by simuPop in the PyOperator call. 21 | :return: True 22 | ''' 23 | pop.vars()['pop_count']=defaultdict(int) 24 | return True 25 | 26 | def classify(subpops,val): 27 | ''' 28 | determine which class each value is in 29 | :param subpops: number of subpops 30 | :param val: the value to check 31 | :return: a list with the category 32 | ''' 33 | res = [] 34 | if val == 1: 35 | res.append('= 1') 36 | if val == 2: 37 | res.append('= 2') 38 | if val < (int(int(subpops)*.05)): 39 | res.append('< 5%') 40 | if val < (int(int(subpops)*.10)): 41 | res.append('< 10%') 42 | if val < (int(int(subpops)*.2)): 43 | res.append('< 20%') 44 | if val < (int(int(subpops)*.5)): 45 | res.append('< 50%') 46 | return res 47 | 48 | def check_k_and_migration_rates(config): 49 | ''' 50 | Check the combination of k and migration rates to see if any combination results in >1.0 total 51 | :param config: the configuration object with the parameters from the command line 52 | :return: output message (if a problem) or True (if no problem 53 | ''' 54 | k_values = config.k_values 55 | output_message = "" 56 | count = 0 57 | migs = config.migrationfraction 58 | for k in k_values: 59 | for mig in migs: 60 | if float(k)*float(mig) >= 1.0: 61 | output_message += "k=%s * mig=%4f is greater than 1.0\n" % (k,mig) 62 | count+=1 63 | if count>0: 64 | return output_message 65 | else: 66 | return True 67 | 68 | 69 | def count_traits_in_subpops(pop, param): 70 | ''' 71 | Count the number of subpops in which each trait occurs (1-numSubPops) 72 | combination in the loci/allele trait space 73 | :param pop: the population object - this is passed by simuPop in the PyOperator call. 74 | :param param: in this case pass the # of loci 75 | :return: True 76 | ''' 77 | (num_loci, numSubPops) = param 78 | sp.stat(pop, haploFreq=range(0,num_loci), vars=['haploFreq_sp', 'haploNum_sp'], subPops=sp.ALL_AVAIL) 79 | 80 | traits_in_subpops = defaultdict(int) 81 | 82 | # now count for all the subpops 83 | for subPop in range(0,numSubPops): 84 | key= list(pop.vars(subPop)['haploNum'].keys()) 85 | #traits_n_counts = pop.vars(subPop)['haploNum'][key[0]] 86 | haplotype_count_map= list(pop.vars(subPop)['haploNum'][key[0]].keys()) 87 | for loci_allele_tuple in haplotype_count_map: 88 | traits_in_subpops[str(loci_allele_tuple)] +=1 89 | 90 | pop.vars()['pop_count'] = traits_in_subpops 91 | 92 | vals=pop.vars()['pop_count'].values() 93 | ones=twos=fivepercent=tenpercent=twentypercent=fiftypercent=0 94 | for val in vals: 95 | if val == 1: 96 | ones += 1 97 | if val == 2: 98 | twos += 1 99 | if val < (int(int(numSubPops) * .05)): 100 | fivepercent +=1 101 | if val < (int(int(numSubPops) * .10)): 102 | tenpercent +=1 103 | if val < (int(int(numSubPops) * .2)): 104 | twentypercent +=1 105 | if val < (int(int(numSubPops) * .5)): 106 | fiftypercent +=1 107 | pop.vars()['ones'].append(ones) 108 | pop.vars()['twos'].append(twos) 109 | pop.vars()['fivepercent'].append(fivepercent) 110 | pop.vars()['tenpercent'].append(tenpercent) 111 | pop.vars()['twentypercent'].append(twentypercent) 112 | pop.vars()['fiftypercent'].append(fiftypercent) 113 | return True 114 | 115 | def init_acumulators(pop, param): 116 | acumulators = param 117 | for acumulator in acumulators: 118 | if acumulator.endswith('_sp'): 119 | pop.vars()[acumulator] = defaultdict(list) 120 | else: 121 | pop.vars()[acumulator] = [] 122 | pop.vars()['allele_frequencies'] = [] 123 | pop.vars()['haplotype_frequencies'] = [] 124 | pop.vars()['allele_count']=[] 125 | pop.vars()['richness'] = [] 126 | pop.vars()['class_freq']=[] 127 | pop.vars()['class_count']=[] 128 | pop.vars()['ones']=[] 129 | pop.vars()['twos']=[] 130 | pop.vars()['fivepercent']=[] 131 | pop.vars()['tenpercent']=[] 132 | pop.vars()['twentypercent']=[] 133 | pop.vars()['fiftypercent'] = [] 134 | #pop.vars()['fst_mean'] 135 | return True 136 | 137 | def update_acumulator(pop, param): 138 | acumulator, var = param 139 | #for acumulator, var in sorted(param.items()): 140 | log.debug("acumulator: %s var: %s" % (acumulator,var)) 141 | if var.endswith('_sp'): 142 | for sp in range(pop.numSubPop()): 143 | pop.vars()[acumulator][sp].append(deepcopy(pop.vars(sp)[var[:-3]])) 144 | else: 145 | pop.vars()[acumulator].append(deepcopy(pop.vars()[var])) 146 | 147 | return True 148 | 149 | def update_richness_acumulator(pop, param): 150 | (acumulator, var) = param 151 | if var.endswith('_sp'): 152 | for sp in range(pop.numSubPop()): 153 | pop.vars()[acumulator][sp].append(len(pop.dvars(sp).alleleFreq[0].values())) 154 | else: 155 | pop.vars()['haplotype_frequencies'].append(len(pop.dvars().haploFreq.values())) 156 | pop.vars()['allele_frequencies'].append(len(pop.dvars().alleleFreq.values())) 157 | pop.vars()['allele_count'].append(len(pop.dvars().alleleNum)) 158 | #pop.vars()[acumulator].append(deepcopy(pop.vars()[var])) 159 | return True 160 | 161 | def calculateAlleleAndGenotypeFrequencies(pop, param): 162 | (popsize, num_loci) = param 163 | 164 | sp.stat(pop, haploFreq = range(0, num_loci), vars=['haploFreq', 'haploNum']) 165 | #sim.stat(pop, alleleFreq = sim.ALL_AVAIL) 166 | 167 | keys = list(pop.dvars().haploFreq.keys()) 168 | 169 | haplotype_map = pop.dvars().haploFreq[keys[0]] 170 | haplotype_count_map = pop.dvars().haploNum[keys[0]] 171 | num_classes = len(haplotype_map) 172 | 173 | #class_freq = {'-'.join(i[0]) : str(i[1]) for i in haplotype_map.items()} 174 | class_freq = dict() 175 | for k,v in haplotype_map.items(): 176 | key = '-'.join(str(x) for x in k) 177 | class_freq[key] = v 178 | #log.debug("class_freq packed: %s", class_freq) 179 | 180 | class_count = dict() 181 | for k,v in haplotype_count_map.items(): 182 | key = '-'.join(str(x) for x in k) 183 | class_count[key] = v 184 | pop.vars()['richness'].append(num_classes) 185 | pop.vars()['class_freq'].append(class_freq) 186 | pop.vars()['class_count'].append(class_count) 187 | 188 | return True 189 | 190 | def constructUniformAllelicDistribution(numalleles): 191 | """Constructs a uniform distribution of N alleles in the form of a frequency list. 192 | Args: 193 | numalleles (int): Number of alleles present in the initial population. 194 | Returns: 195 | (list): Array of floats, giving the initial frequency of N alleles. 196 | 197 | """ 198 | divisor = 100.0 / numalleles 199 | frac = divisor / 100.0 200 | distribution = [frac] * numalleles 201 | return distribution 202 | 203 | def mean_confidence_interval(data, confidence=0.95): 204 | a = 1.0 * np.array(data) 205 | n = len(a) 206 | m, se = np.mean(a), scipy.stats.sem(a) 207 | h = se * scipy.stats.t.ppf((1 + confidence) / 2., n-1) 208 | return m, m-h, m+h 209 | 210 | def setup_output(experiment="test"): 211 | # first create output directories-- should exist already but if not.... 212 | path = os.getcwd() 213 | path = path + "/output/" 214 | try: 215 | os.mkdir(path) 216 | except OSError: 217 | print("Creation of the general output directory %s failed. But it might exist. That's okay - carry on. " % path) 218 | else: 219 | print("Successfully created the general output directory %s " % path) 220 | path = path + experiment 221 | 222 | ## now add the specific project path... This should exist (if it does we are writing over old stuff) 223 | try: 224 | os.mkdir(path) 225 | except OSError: 226 | print("Creation of the project output directory %s failed - it might already exist? Quitting... " % path) 227 | sys.exit() 228 | else: 229 | print("Successfully created the project output directory %s " % path) 230 | return path 231 | 232 | 233 | def save_parameters(args, config, output_path): 234 | ''' 235 | Save the arguments used to run the experiment into the output director 236 | :param args: the sys.argv string that contains the raw input 237 | :param config: a list of all the possible configuration options 238 | :param output_path: a path to the output directory 239 | :return: True if completed 240 | ''' 241 | f = open(output_path + "/parameters.txt", "w+") 242 | f.write("Arguments used: %s\n" % args) 243 | f.write("--experiment: %s\n" % config.experiment) 244 | f.write("--debug: %s\n" % config.debug) 245 | f.write("--reps: %s\n" % config.reps) 246 | f.write("--networkfile: %s\n"% config.networkfile) 247 | f.write("--debug: %s\n" % config.debug) 248 | f.write("--numloci: %s\n" % config.numloci) 249 | f.write("--maxinittraits: %s\n" % config.maxinittraits) 250 | f.write("--innovrate: %s\n" % config.innovrate) 251 | f.write("--simlength: %s\n" % config.simlength) 252 | f.write("--popsize: %s\n" % config.popsize) 253 | f.write("--migrationfraction: %s\n" % config.migrationfraction) 254 | f.write("--seed: %s\n" % config.seed) 255 | f.write("--k_values: %s\n" % config.k_values) 256 | f.write("--sub_pops: %s\n" % config.sub_pops) 257 | f.write("--maxalleles: %s\n" % config.maxalleles) 258 | f.write("--save_figs: %s\n" % config.save_figs) 259 | f.write("--burnintime: %s\n" % config.burnintime) 260 | f.write("--rewiringprob: %s\n" % config.rewiringprob) 261 | f.close() 262 | 263 | return True 264 | -------------------------------------------------------------------------------- /models/parameter-sweep-for-localization.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from collections import defaultdict, OrderedDict 3 | from copy import deepcopy 4 | import psutil 5 | import simuOpt 6 | simuOpt.setOptions(alleleType='long', optimized=True, quiet=False) 7 | simuOpt.setOptions(numThreads=psutil.cpu_count()) 8 | import math 9 | import seaborn as sns 10 | sns.set_style('white') 11 | import matplotlib.pyplot as plt 12 | import networkdrift.demography.network as network 13 | from networkdrift.utils import utils 14 | import simuPOP as sp 15 | import logging as log 16 | import numpy as np 17 | import scipy.stats 18 | import argparse 19 | import uuid 20 | from time import time 21 | import sys 22 | import os 23 | from collections import defaultdict 24 | import csv 25 | 26 | global config, sim_id, script, cores 27 | 28 | ''' 29 | Example use of script. 30 | parameter-sweep-for-localization.py 31 | --experiment paramsweep5 --networkfile smallworld --numloci 1 --maxinittraits 100 32 | --popsize 5000 --migrationfraction 0.0005 0.001 0.0025 0.005 33 | --innovrate 0.00 0.001 0.005 0.01 34 | --k_values 2 25 50 100 125 35 | --rewiringprob 0.001 --sub_pops 200 --maxalleles 10000 --simlength 2005 --reps 5 36 | 37 | example using gml file 38 | parameter-sweep-for-localization.py 39 | --experiment rn-sweep 40 | --networkfile /Users/clipo/Documents/PycharmProjects/network-drift/data/rapa_nui/ahu.gml 41 | --numloci 1 --innovrate 0.00 --maxinittraits 100 42 | --popsize 5000 --migrationfraction 0.000001 0.000005 0.00001 0.00005 43 | --k_values 5 10 25 50 75 100 125 --rewiringprob 0.0 44 | --sub_pops 150 --maxalleles 10000 --simlength 2005 --reps 5 45 | ''' 46 | 47 | output=defaultdict(dict) 48 | 49 | def setup(parser): 50 | config = parser.parse_args() 51 | sim_id = uuid.uuid1().urn 52 | script = __file__ 53 | 54 | if config.debug == '1': 55 | log.basicConfig(level=log.DEBUG, format='%(asctime)s %(levelname)s: %(message)s') 56 | else: 57 | log.basicConfig(level=log.INFO, format='%(asctime)s %(levelname)s: %(message)s') 58 | 59 | def main(): 60 | parser = argparse.ArgumentParser() 61 | parser.add_argument("--experiment", help="provide name for experiment", required=True, type=str, default="test") 62 | parser.add_argument("--debug", help="turn on debugging output") 63 | parser.add_argument("--reps", help="Replicated populations per parameter set", type=int, default=3) 64 | parser.add_argument("--networkfile", help="Name of GML file representing the network model for this simulation", 65 | required=True, type=str, default="smallworld") 66 | parser.add_argument("--numloci", help="Number of loci per individual (use with care)", type=int, required=True, default=1) 67 | parser.add_argument("--maxinittraits", help="Max initial number of traits per locus for initialization", type=int, 68 | required=True, default=50) 69 | parser.add_argument("--innovrate", nargs='+', help="Rate(s) at which innovations occur in population as a per-locus rate", type=float, default=[]) 70 | parser.add_argument("--simlength", help="Time at which simulation and sampling end, defaults to 3000 generations", 71 | type=int, default="20") 72 | parser.add_argument("--popsize", help="Initial size of population for each community in the model", type=int, required=True) 73 | parser.add_argument("--migrationfraction", nargs='+', help="Fraction of population that migrates each time step", type=float, required=True, default=[]) 74 | parser.add_argument("--seed", type=int, help="Seed for random generators to ensure replicability") 75 | parser.add_argument( "--k_values", nargs='+', type=int, help="list of k-values to explore [e.g., 2 4 20 24]", default=[]) 76 | parser.add_argument("--sub_pops", nargs="+", help="Number of sub populations", required=True, default=[]) 77 | parser.add_argument("--maxalleles", type=int, help="Maximum number of alleles", default=50) 78 | parser.add_argument("--save_figs", type=bool, help="Save figures or not?", default=False) 79 | parser.add_argument("--burnintime", type=int, help="How long to wait before making measurements? ", default=2000) 80 | parser.add_argument("--rewiringprob", type=float, help="Probability of random rewiring", default=0) 81 | 82 | config = parser.parse_args() 83 | 84 | # setup output directories for writing 85 | output_path = utils.setup_output(config.experiment) 86 | 87 | # check the k and migration rate combinations 88 | check = utils.check_k_and_migration_rates(config) 89 | if check is not True: 90 | print("\nProblem(s):\t %s\n" % check) 91 | print("Please adjust input values for k and/or migration rate and restart.\n ") 92 | sys.exit() 93 | else: 94 | print("\nChecked on the migration and k values -- all looks good!\n") 95 | 96 | # save parameters 97 | utils.save_parameters(str(sys.argv), config, output_path) 98 | 99 | # set up the frequencies for the alleles in each loci. Here assuming a uniform distribution as a starting point 100 | distribution = utils.constructUniformAllelicDistribution(config.maxinittraits) 101 | 102 | # prepare file for output 103 | output_data_file_name = "%s/%s-rare-trait-output.csv" % (output_path, config.experiment) 104 | with open(output_data_file_name, mode='w') as output_file: 105 | output_writer = csv.writer(output_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) 106 | output_writer.writerow(["Iteration", "k", "NumSubPops", "Migration", "InnovationRate", "Ones_Mean", 107 | "Ones_95%_Lower", "Ones_95%_Upper", "Twos_Mean", "Twos_95%_Lower", "Twos_95%_Upper", "Richness_Mean", 108 | "Richness_95%_Lower", "Richness_95%_Upper","Fst_Mean","Fst_95%_Lower","Fst_95%_Upper"]) 109 | output_file.flush() 110 | subpop_run_values = config.sub_pops 111 | k_run_values = config.k_values 112 | mig_run_values = config.migrationfraction 113 | innov_run_values = config.innovrate 114 | 115 | iteration=-1 116 | 117 | for subpop in subpop_run_values: 118 | if k_run_values == [0]: 119 | k_run_values = [2, int(float(subpop) * .1), int(float(subpop) * .2), 120 | int(float(subpop) * .5), int(float(subpop) * .8), 121 | int(float(subpop) * .9), 122 | int(subpop) - 1] 123 | 124 | for k in k_run_values: 125 | for mig in mig_run_values: 126 | for innov in innov_run_values: 127 | ## let us know whats happening 128 | iteration += 1 129 | print("Now running with subpops: %s k-value: %s mig rate: %4f innov rate: %4f" % (subpop,k,mig,innov)) 130 | ## these are lists of things that simuPop will do at different stages 131 | init_ops = OrderedDict() 132 | pre_ops = OrderedDict() 133 | post_ops = OrderedDict() 134 | 135 | # Construct a demographic model 136 | #networkmodel = NetworkModel( networkmodel="/Users/clipo/Documents/PycharmProjects/RapaNuiSim/notebooks/test_graph.gml", 137 | networkmodel = network.NetworkModel( networkmodel=config.networkfile, 138 | simulation_id=config.experiment, 139 | sim_length=config.simlength, 140 | burn_in_time=config.burnintime, 141 | initial_subpop_size=config.popsize, 142 | migrationfraction=mig, 143 | sub_pops=subpop, 144 | connectedness=k, # if 0, then distance decay 145 | save_figs=config.save_figs, 146 | network_iteration=iteration) 147 | 148 | num_pops = networkmodel.get_subpopulation_number() 149 | sub_pop_size = int(config.popsize / num_pops) 150 | 151 | # The regional network model defines both of these, in order to configure an initial population for evolution 152 | # Construct the initial population 153 | pops = sp.Population(size = [sub_pop_size]*num_pops, 154 | subPopNames = str(list(networkmodel.get_subpopulation_names())), 155 | infoFields = 'migrate_to', 156 | ploidy=1, 157 | loci=config.numloci ) 158 | 159 | ### now set up the activities 160 | init_ops['acumulators'] = sp.PyOperator(utils.init_acumulators, param=['fst','alleleFreq', 'haploFreq']) 161 | init_ops['subpop_counts'] = sp.PyOperator(utils.init_count_traits_in_subpops) 162 | init_ops['Sex'] = sp.InitSex() 163 | init_ops['Freq'] = sp.InitGenotype(loci=list(range(config.numloci)),freq=distribution) 164 | 165 | post_ops['Innovate'] = sp.KAlleleMutator(k=config.maxalleles, rates=innov, loci=sp.ALL_AVAIL) 166 | post_ops['mig']=sp.Migrator(rate=networkmodel.get_migration_matrix()) #, reps=[3]) 167 | post_ops['Stat-fst'] = sp.Stat(structure=sp.ALL_AVAIL) 168 | post_ops['Stat-richness']=sp.Stat(alleleFreq=[0], haploFreq=[0], vars=['alleleFreq','haploFreq','alleleNum', 'genoNum']) 169 | post_ops['fst_acumulation'] = sp.PyOperator(utils.update_acumulator, param=['fst','F_st']) 170 | post_ops['richness_acumulation'] = sp.PyOperator(utils.update_richness_acumulator, param=('alleleFreq', 'Freq of Alleles')) 171 | post_ops['class_richness']=sp.PyOperator(utils.calculateAlleleAndGenotypeFrequencies, param=(config.popsize,config.numloci)) 172 | post_ops['count_traits_in_subpops'] = sp.PyOperator(utils.count_traits_in_subpops, param=(config.numloci,num_pops), subPops=sp.ALL_AVAIL) 173 | 174 | mating_scheme = sp.RandomSelection() 175 | 176 | ## go simuPop go! evolve your way to the future! 177 | sim = sp.Simulator(pops, rep=config.reps) 178 | sim.evolve(initOps=list(init_ops.values()), preOps=list(pre_ops.values()), postOps=list(post_ops.values()), 179 | matingScheme=mating_scheme, gen=config.simlength) 180 | 181 | count=0 182 | for pop in sim.populations(): 183 | output[count] = deepcopy(pop.dvars()) 184 | count+=1 185 | 186 | ones_point_in_time = [] 187 | twos_point_in_time = [] 188 | richness_point_in_time = [] 189 | fst_point_in_time = [] 190 | 191 | for n in range(config.reps): 192 | list_of_ones = list(output[n].ones) 193 | list_of_twos = list(output[n].twos) 194 | list_of_richness = list(output[n].richness) 195 | list_of_fst = list(output[n].fst) 196 | ones_point_in_time.append(list_of_ones[2000]) 197 | twos_point_in_time.append(list_of_twos[2000]) 198 | richness_point_in_time.append(list_of_richness[2000]) 199 | fst_point_in_time.append(list_of_fst[2000]) 200 | 201 | (ones_ave, ones_min, ones_max) = utils.mean_confidence_interval(ones_point_in_time, 202 | confidence=0.95) 203 | (twos_ave, twos_min, twos_max) = utils.mean_confidence_interval(twos_point_in_time, 204 | confidence=0.95) 205 | (richness_ave, richness_min, richness_max) = utils.mean_confidence_interval(richness_point_in_time, 206 | confidence=0.95) 207 | (fst_ave, fst_min, fst_max) = utils.mean_confidence_interval(fst_point_in_time, 208 | confidence=0.95) 209 | 210 | output_writer.writerow([iteration,k,subpop,mig,innov,ones_ave,ones_min,ones_max, 211 | twos_ave,twos_min,twos_max,richness_ave,richness_min,richness_max,fst_ave,fst_min,fst_max]) 212 | output_file.flush() 213 | 214 | if __name__ == "__main__": 215 | main() 216 | 217 | -------------------------------------------------------------------------------- /models/network-subpop-eval.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from collections import defaultdict, OrderedDict 3 | from copy import deepcopy 4 | import simuOpt 5 | simuOpt.setOptions(alleleType='long', optimized=True, quiet=False) 6 | import math 7 | import seaborn as sns 8 | sns.set_style('white') 9 | import matplotlib.pyplot as plt 10 | import networkdrift.demography.network as network 11 | from networkdrift.utils import utils 12 | import simuPOP as sp 13 | import logging as log 14 | import numpy as np 15 | import scipy.stats 16 | import argparse 17 | import uuid 18 | from matplotlib import colors as mcolors 19 | from time import time 20 | import sys 21 | import os 22 | from collections import defaultdict 23 | 24 | global config, sim_id, script, cores 25 | 26 | output=defaultdict(dict) 27 | 28 | def setup(parser): 29 | config = parser.parse_args() 30 | sim_id = uuid.uuid1().urn 31 | script = __file__ 32 | 33 | if config.debug == '1': 34 | log.basicConfig(level=log.DEBUG, format='%(asctime)s %(levelname)s: %(message)s') 35 | else: 36 | log.basicConfig(level=log.INFO, format='%(asctime)s %(levelname)s: %(message)s') 37 | 38 | def main(): 39 | parser = argparse.ArgumentParser() 40 | parser.add_argument("--experiment", help="provide name for experiment", required=True, type=str, default="test") 41 | parser.add_argument("--debug", help="turn on debugging output") 42 | parser.add_argument("--reps", help="Replicated populations per parameter set", type=int, default=1) 43 | parser.add_argument("--networkfile", help="Name of GML file representing the network model for this simulation", 44 | required=True, type=str) 45 | parser.add_argument("--numloci", help="Number of loci per individual", type=int, required=True) 46 | parser.add_argument("--maxinittraits", help="Max initial number of traits per locus for initialization", type=int, 47 | required=True) 48 | parser.add_argument("--innovrate", help="Rate at which innovations occur in population as a per-locus rate", type=float, default=0.001) 49 | parser.add_argument("--simlength", help="Time at which simulation and sampling end, defaults to 3000 generations", 50 | type=int, default="20") 51 | parser.add_argument("--popsize", help="Initial size of population for each community in the model", type=int, required=True) 52 | parser.add_argument("--migrationfraction", nargs='+', help="Fraction of population that migrates each time step", 53 | type=float, required=True, default=[]) 54 | parser.add_argument("--seed", type=int, help="Seed for random generators to ensure replicability") 55 | parser.add_argument( "--k_values", nargs='+', type=int, help="list of k-values to explore [e.g., 2 4 20 24", default=[]) 56 | parser.add_argument("--sub_pops", nargs='+', help="Number of sub populations", required=True, default=[10]) 57 | parser.add_argument("--maxalleles", type=int, help="Maximum number of alleles", default=50) 58 | parser.add_argument("--save_figs", type=bool, help="Save figures or not?", default=True) 59 | parser.add_argument("--burnintime", type=int, help="How long to wait before making measurements? ", default=2000) 60 | parser.add_argument("--rewiringprob", type=float, help="Probability of random rewiring", default=0) 61 | 62 | config = parser.parse_args() 63 | 64 | # check the k and migration rate combinations 65 | for kvalue in config.k_values: 66 | if float(kvalue) * float(config.migrationfraction) >= 1.0: 67 | print("k=%s * mig=%4f is greater than 1.0\n" % (kvalue, config.migrationfraction)) 68 | print("Please adjust input values for k and/or migration rate and restart.\n ") 69 | sys.exit() 70 | 71 | # setup output directories for writing 72 | output_path = utils.setup_output(config.experiment) 73 | 74 | # save parameters 75 | utils.save_parameters(str(sys.argv), config, output_path) 76 | 77 | k_run_values=config.k_values 78 | subpop_run_values = config.sub_pops 79 | 80 | ## make sure the k values are less than # of subpops and > 1 81 | for k in k_run_values: 82 | for subnum in subpop_run_values: 83 | if int(k) > int(subnum) or int(k) < 2: 84 | print("k values can not be greater than the number of sub populations. k = %s subpops = %s \n" % (k, subnum)) 85 | sys.exit() 86 | 87 | ## initialize the output dictionary 88 | for k in k_run_values: 89 | for sb in subpop_run_values: 90 | output[k][sb]={} 91 | 92 | # set up the frequencies for the alleles in each loci. Here assuming a uniform distribution as a starting point 93 | distribution = utils.constructUniformAllelicDistribution(config.maxinittraits) 94 | 95 | iteration_number=-1 96 | for k in k_run_values: 97 | for subnum in subpop_run_values: 98 | iteration_number += 1 99 | ## these are lists of things that simuPop will do at different stages 100 | init_ops = OrderedDict() 101 | pre_ops = OrderedDict() 102 | post_ops = OrderedDict() 103 | 104 | # Construct a demographic model from a collection of network slices which represent a temporal network 105 | # of changing subpopulations and interaction strengths. This object is Callable, and simply is handed 106 | # to the mating function which applies it during the copying process 107 | #networkmodel = NetworkModel( networkmodel="/Users/clipo/Documents/PycharmProjects/RapaNuiSim/notebooks/test_graph.gml", 108 | networkmodel = network.NetworkModel( networkmodel="smallworld", 109 | simulation_id=config.experiment, 110 | sim_length=config.simlength, 111 | burn_in_time=config.burnintime, 112 | initial_subpop_size=config.popsize, 113 | migrationfraction=config.migrationfraction, 114 | sub_pops=subnum, 115 | connectedness=k, # if 0, then distance decay 116 | save_figs=config.save_figs, 117 | network_iteration=iteration_number) 118 | 119 | num_pops = networkmodel.get_subpopulation_number() 120 | sub_pop_size = int(config.popsize / num_pops) 121 | 122 | # The regional network model defines both of these, in order to configure an initial population for evolution 123 | # Construct the initial population 124 | pops = sp.Population(size = [sub_pop_size]*num_pops, 125 | subPopNames = str(list(networkmodel.get_subpopulation_names())), 126 | infoFields = 'migrate_to', 127 | ploidy=1, 128 | loci=config.numloci ) 129 | 130 | ### now set up the activities 131 | init_ops['acumulators'] = sp.PyOperator(utils.init_acumulators, param=['fst','alleleFreq', 'haploFreq']) 132 | init_ops['Sex'] = sp.InitSex() 133 | 134 | init_ops['Freq'] = sp.InitGenotype(loci=list(range(config.numloci)),freq=distribution) 135 | 136 | post_ops['Innovate'] = sp.KAlleleMutator(k=config.maxalleles, rates=config.innovrate, loci=sp.ALL_AVAIL) 137 | post_ops['mig']=sp.Migrator(rate=networkmodel.get_migration_matrix()) #, reps=[3]) 138 | #for i, mig in enumerate(migs): 139 | # post_ops['mig-%d' % i] = sp.Migrator(demography.migrIslandRates(mig, num_pops), reps=[i]) 140 | 141 | post_ops['Stat-fst'] = sp.Stat(structure=sp.ALL_AVAIL) 142 | 143 | post_ops['Stat-richness']=sp.Stat(alleleFreq=[0], haploFreq=[0], vars=['alleleFreq','haploFreq','alleleNum', 'genoNum']) 144 | post_ops['fst_acumulation'] = sp.PyOperator(utils.update_acumulator, param=['fst','F_st']) 145 | post_ops['richness_acumulation'] = sp.PyOperator(utils.update_richness_acumulator, param=('alleleFreq', 'Freq of Alleles')) 146 | post_ops['class_richness']=sp.PyOperator(utils.calculateAlleleAndGenotypeFrequencies, param=(config.popsize,config.numloci)) 147 | 148 | mating_scheme = sp.RandomSelection() 149 | #mating_scheme=sp.RandomSelection(subPopSize=sub_pop_size) 150 | 151 | ## go simuPop go! evolve your way to the future! 152 | sim = sp.Simulator(pops, rep=config.reps) 153 | print("now evolving... k = %s with sub_pops = %s" % (k,subnum)) 154 | sim.evolve(initOps=list(init_ops.values()), preOps=list(pre_ops.values()), postOps=list(post_ops.values()), 155 | matingScheme=mating_scheme, gen=config.simlength) 156 | 157 | # now make a figure of the Fst results 158 | fig = plt.figure(figsize=(16, 9)) 159 | ax = fig.add_subplot(111) 160 | count=0 161 | for pop in sim.populations(): 162 | ax.plot(pop.dvars().fst, label='Replicate: %s' % count) 163 | output[k][subnum][count] = deepcopy(pop.dvars()) 164 | count += 1 165 | ax.legend(loc=2) 166 | ax.set_ylabel('FST') 167 | ax.set_xlabel('Generation') 168 | plt.show() 169 | 170 | sum_fig = plt.figure(figsize=(16,9)) 171 | ax=sum_fig.add_subplot(111) 172 | iteration=-1 173 | for k in k_run_values: 174 | for subnum in subpop_run_values: 175 | iteration += 1 176 | # only label the first one 177 | for n in range(config.reps): 178 | if n==0: 179 | ax.plot(output[k][subnum][n].fst, color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration], label='k = %s subpops = %s' % (k, subnum)) 180 | else: 181 | ax.plot(output[k][subnum][n].fst, 182 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration]) 183 | ax.legend(loc=2) 184 | ax.set_ylabel('Fst') 185 | ax.set_xlabel('Generations') 186 | plt.show() 187 | savefilename= output_path + "/sum_fig.png" 188 | sum_fig.savefig(savefilename, bbox_inches='tight') 189 | 190 | rich_fig = plt.figure(figsize=(16,9)) 191 | ax=rich_fig.add_subplot(111) 192 | iteration=-1 193 | for k in k_run_values: 194 | for sb in subpop_run_values: 195 | iteration+=1 196 | # only add a label for the first one (not all the replicates) 197 | for n in range(config.reps): 198 | if n==0: 199 | ax.plot(output[k][subnum][n].richness, 200 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration], label='k = %s subpops = %s' % (k, subnum)) 201 | else: 202 | ax.plot(output[k][subnum][n].richness, 203 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration]) 204 | ax.legend(loc=2) 205 | ax.set_ylabel('Richness') 206 | ax.set_xlabel('Generations') 207 | plt.show() 208 | savefilename = output_path + "/richness.png" 209 | rich_fig.savefig(savefilename, bbox_inches='tight') 210 | 211 | ## output CI for the parameters 212 | 213 | summary_fig = plt.figure(figsize=(16, 9)) 214 | ax = summary_fig.add_subplot(111) 215 | 216 | iteration = -1 217 | for k in k_run_values: 218 | for subnum in subpop_run_values: 219 | iteration += 1 220 | CI_average = [] 221 | CI_min = [] 222 | CI_max = [] 223 | for t in range(len(output[k][subnum][0].fst)): 224 | point_in_time = [] 225 | for n in range(config.reps): 226 | list_of_points = list(output[k][subnum][n].fst) 227 | point_in_time.append(list_of_points[t]) 228 | (ave, min, max) = utils.mean_confidence_interval(point_in_time, confidence=0.95) 229 | CI_average.append(ave) 230 | CI_min.append(min) 231 | CI_max.append(max) 232 | ax.plot(list(CI_average), color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration],label='k = %s subpops = %s' % (k, subnum)) 233 | ax.plot(list(CI_min), "--", color="0.5") 234 | ax.plot(list(CI_max), "--", color="0.5") 235 | ax.fill_between(list(CI_average), list(CI_max), list(CI_min), color="None", linestyle="--") 236 | ax.legend(loc=2) 237 | ax.set_ylabel('Fst') 238 | ax.set_xlabel('Generation') 239 | plt.show() 240 | savefilename = output_path + "/summary-ci.png" 241 | summary_fig.savefig(savefilename, bbox_inches='tight') 242 | 243 | ## now the richness graph 244 | richness_sum_fig = plt.figure(figsize=(16, 9)) 245 | ax = richness_sum_fig.add_subplot(111) 246 | 247 | iteration=-1 248 | for k in k_run_values: 249 | for subnum in subpop_run_values: 250 | iteration += 1 251 | CI_average = [] 252 | CI_min = [] 253 | CI_max = [] 254 | for t in range(len(output[k][subnum][0].richness)): 255 | point_in_time = [] 256 | for n in range(config.reps): 257 | list_of_points = list(output[k][subnum][n].richness) 258 | point_in_time.append(list_of_points[t]) 259 | (ave, min, max) = utils.mean_confidence_interval(point_in_time, confidence=0.95) 260 | CI_average.append(ave) 261 | CI_min.append(min) 262 | CI_max.append(max) 263 | ax.plot(list(CI_average), color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration],label='k = %s subpops = %s' % (k, subnum)) 264 | ax.plot(list(CI_min), "--", color="0.5") 265 | ax.plot(list(CI_max), "--", color="0.5") 266 | ax.fill_between(list(CI_average), list(CI_max), list(CI_min), color="None", linestyle="--") 267 | ax.legend(loc=2) 268 | ax.set_ylabel('Richness') 269 | ax.set_xlabel('Generation') 270 | plt.show() 271 | savefilename = output_path + "/richness-ci.png" 272 | richness_sum_fig.savefig(savefilename, bbox_inches='tight') 273 | 274 | if __name__ == "__main__": 275 | main() 276 | 277 | -------------------------------------------------------------------------------- /models/network-k-eval.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from collections import defaultdict, OrderedDict 3 | from copy import deepcopy 4 | import simuOpt 5 | simuOpt.setOptions(alleleType='long', optimized=True, quiet=False) 6 | import math 7 | import seaborn as sns 8 | sns.set_style('white') 9 | import matplotlib.pyplot as plt 10 | import networkdrift.demography.network as network 11 | from networkdrift.utils import utils 12 | import simuPOP as sp 13 | import logging as log 14 | import numpy as np 15 | import scipy.stats 16 | import argparse 17 | import uuid 18 | from matplotlib import colors as mcolors 19 | from time import time 20 | import sys 21 | import os 22 | from collections import defaultdict 23 | 24 | global config, sim_id, script, cores 25 | 26 | output=defaultdict(dict) 27 | 28 | def setup(parser): 29 | config = parser.parse_args() 30 | sim_id = uuid.uuid1().urn 31 | script = __file__ 32 | 33 | if config.debug == '1': 34 | log.basicConfig(level=log.DEBUG, format='%(asctime)s %(levelname)s: %(message)s') 35 | else: 36 | log.basicConfig(level=log.INFO, format='%(asctime)s %(levelname)s: %(message)s') 37 | 38 | def main(): 39 | parser = argparse.ArgumentParser() 40 | parser.add_argument("--experiment", help="provide name for experiment", required=True, type=str, default="test") 41 | parser.add_argument("--debug", help="turn on debugging output") 42 | parser.add_argument("--reps", help="Replicated populations per parameter set", type=int, default=1) 43 | parser.add_argument("--networkfile", help="Name of GML file representing the network model for this simulation", 44 | required=True, type=str) 45 | parser.add_argument("--numloci", help="Number of loci per individual", type=int, required=True) 46 | parser.add_argument("--maxinittraits", help="Max initial number of traits per locus for initialization", type=int, 47 | required=True) 48 | parser.add_argument("--innovrate", help="Rate at which innovations occur in population as a per-locus rate", type=float, default=0.001) 49 | parser.add_argument("--simlength", help="Time at which simulation and sampling end, defaults to 3000 generations", 50 | type=int, default="20") 51 | parser.add_argument("--popsize", help="Initial size of population for each community in the model", type=int, required=True) 52 | parser.add_argument("--migrationfraction", help="Fraction of population that migrates each time step", 53 | type=float, required=True, default=0.0001) 54 | parser.add_argument("--seed", type=int, help="Seed for random generators to ensure replicability") 55 | parser.add_argument( "--k_values", nargs='+', type=int, help="list of k-values to explore [e.g., 2 4 20 24", default=[]) 56 | parser.add_argument("--sub_pops", type=int, help="Number of sub populations", required=True, default=10) 57 | parser.add_argument("--maxalleles", type=int, help="Maximum number of alleles", default=50) 58 | parser.add_argument("--save_figs", type=bool, help="Save figures or not?", default=True) 59 | parser.add_argument("--burnintime", type=int, help="How long to wait before making measurements? ", default=2000) 60 | parser.add_argument("--rewiringprob", type=float, help="Probability of random rewiring", default=0) 61 | 62 | config = parser.parse_args() 63 | 64 | # check the k and migration rate combinations 65 | for kvalue in list(config.k_values): 66 | if float(kvalue) * float(config.migrationfraction) >= 1.0: 67 | print("k=%s * mig=%4f is greater than 1.0\n" % (kvalue, config.migrationfraction)) 68 | print("Please adjust input values for k and/or migration rate and restart.\n ") 69 | sys.exit() 70 | 71 | # setup output directories for writing 72 | output_path = utils.setup_output(config.experiment) 73 | 74 | # save parameters 75 | utils.save_parameters(str(sys.argv), config, output_path) 76 | 77 | run_param=config.k_values 78 | 79 | 80 | ## initialize the output dictionary 81 | for k in run_param: 82 | output[k]={} 83 | if k >= config.sub_pops: 84 | print("k values cannot be greater than the number of sub pops.\n") 85 | sys.exit() 86 | 87 | # set up the frequencies for the alleles in each loci. Here assuming a uniform distribution as a starting point 88 | distribution = utils.constructUniformAllelicDistribution(config.maxinittraits) 89 | 90 | iteration_number=-1 91 | for param_value in run_param: 92 | iteration_number += 1 93 | ## these are lists of things that simuPop will do at different stages 94 | init_ops = OrderedDict() 95 | pre_ops = OrderedDict() 96 | post_ops = OrderedDict() 97 | 98 | # Construct a demographic model from a collection of network slices which represent a temporal network 99 | # of changing subpopulations and interaction strengths. This object is Callable, and simply is handed 100 | # to the mating function which applies it during the copying process 101 | #networkmodel = NetworkModel( networkmodel="/Users/clipo/Documents/PycharmProjects/RapaNuiSim/notebooks/test_graph.gml", 102 | networkmodel = network.NetworkModel( networkmodel=config.networkfile, 103 | simulation_id=config.experiment, 104 | sim_length=config.simlength, 105 | burn_in_time=config.burnintime, 106 | initial_subpop_size=config.popsize, 107 | migrationfraction=config.migrationfraction, 108 | sub_pops=int(config.sub_pops), 109 | connectedness=param_value, # if 0, then distance decay 110 | save_figs=config.save_figs, 111 | network_iteration=iteration_number, 112 | output_path=output_path) 113 | 114 | num_pops = networkmodel.get_subpopulation_number() 115 | if param_value >= num_pops: 116 | print("k values cannot be greater or equal to the number of sub pops.\n") 117 | sys.exit() 118 | sub_pop_size = int(config.popsize / num_pops) 119 | 120 | # The regional network model defines both of these, in order to configure an initial population for evolution 121 | # Construct the initial population 122 | pops = sp.Population(size = [sub_pop_size]*num_pops, 123 | subPopNames = str(list(networkmodel.get_subpopulation_names())), 124 | infoFields = 'migrate_to', 125 | ploidy=1, 126 | loci=config.numloci ) 127 | 128 | ### now set up the activities 129 | init_ops['acumulators'] = sp.PyOperator(utils.init_acumulators, param=['fst','alleleFreq', 'haploFreq']) 130 | init_ops['subpop_counts'] = sp.PyOperator(utils.init_count_traits_in_subpops) 131 | init_ops['Sex'] = sp.InitSex() 132 | init_ops['Freq'] = sp.InitGenotype(loci=list(range(config.numloci)),freq=distribution) 133 | 134 | post_ops['Innovate'] = sp.KAlleleMutator(k=config.maxalleles, rates=config.innovrate, loci=sp.ALL_AVAIL) 135 | post_ops['mig']=sp.Migrator(rate=networkmodel.get_migration_matrix()) #, reps=[3]) 136 | post_ops['Stat-fst'] = sp.Stat(structure=sp.ALL_AVAIL) 137 | post_ops['Stat-richness']=sp.Stat(alleleFreq=[0], haploFreq=[0], vars=['alleleFreq','haploFreq','alleleNum', 'genoNum']) 138 | post_ops['fst_acumulation'] = sp.PyOperator(utils.update_acumulator, param=['fst','F_st']) 139 | post_ops['richness_acumulation'] = sp.PyOperator(utils.update_richness_acumulator, param=('alleleFreq', 'Freq of Alleles')) 140 | post_ops['class_richness']=sp.PyOperator(utils.calculateAlleleAndGenotypeFrequencies, param=(config.popsize,config.numloci)) 141 | post_ops['count_traits_in_subpops'] = sp.PyOperator(utils.count_traits_in_subpops, param=(config.numloci,num_pops), subPops=sp.ALL_AVAIL) 142 | 143 | mating_scheme = sp.RandomSelection() 144 | 145 | ## go simuPop go! evolve your way to the future! 146 | sim = sp.Simulator(pops, rep=config.reps) 147 | print("now evolving... k= %s" % param_value) 148 | sim.evolve(initOps=list(init_ops.values()), preOps=list(pre_ops.values()), postOps=list(post_ops.values()), 149 | matingScheme=mating_scheme, gen=config.simlength) 150 | 151 | # now make a figure of the Fst results 152 | fig = plt.figure(figsize=(16, 9)) 153 | ax = fig.add_subplot(111) 154 | count=0 155 | for pop in sim.populations(): 156 | ax.plot(pop.dvars().fst, label='Replicate: %s' % count) 157 | output[param_value][count] = deepcopy(pop.dvars()) 158 | count += 1 159 | ax.legend(loc=2) 160 | ax.set_ylabel('FST') 161 | ax.set_xlabel('Generation') 162 | plt.show() 163 | 164 | ## draw traits in 1s or 2s of the subpops 165 | subpop_fig = plt.figure(figsize=(16,9)) 166 | ax=subpop_fig.add_subplot(111) 167 | iteration = -1 168 | for k in run_param: 169 | iteration += 1 170 | # only label the first one 171 | for n in range(config.reps): 172 | if n == 0: 173 | #print(output[k][n].ones) 174 | ax.plot(output[k][n].ones, 175 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration], 176 | label='k = %s - traits in just one subpopulation' % k) 177 | ax.plot(output[k][n].twos, "--", 178 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration], 179 | label='k = %s - traits in just two or fewer subpopulations'% k) 180 | else: 181 | ax.plot(output[k][n].ones, 182 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration]) 183 | ax.plot(output[k][n].twos,"--", 184 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration]) 185 | ax.legend(loc=2) 186 | ax.set_ylabel('Numbers of Traits') 187 | ax.set_xlabel('Generations') 188 | plt.show() 189 | savefilename = output_path + "/subpop_fig.svg" 190 | subpop_fig.savefig(savefilename, bbox_inches='tight') 191 | 192 | sum_fig = plt.figure(figsize=(16,9)) 193 | ax=sum_fig.add_subplot(111) 194 | iteration=-1 195 | for k in run_param: 196 | iteration += 1 197 | # only label the first one 198 | for n in range(config.reps): 199 | if n==0: 200 | ax.plot(output[k][n].fst, color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration], label='k = %s' % k) 201 | else: 202 | ax.plot(output[k][n].fst, 203 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration]) 204 | ax.legend(loc=2) 205 | ax.set_ylabel('Fst') 206 | ax.set_xlabel('Generations') 207 | plt.show() 208 | savefilename= output_path + "/sum_fig.svg" 209 | sum_fig.savefig(savefilename, bbox_inches='tight') 210 | 211 | rich_fig = plt.figure(figsize=(16,9)) 212 | ax=rich_fig.add_subplot(111) 213 | iteration=-1 214 | for k in run_param: 215 | iteration+=1 216 | # only add a label for the first one (not all the replicates) 217 | for n in range(config.reps): 218 | if n==0: 219 | ax.plot(output[k][n].richness, 220 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration], label = 'k = %s' % k) 221 | else: 222 | ax.plot(output[k][n].richness, 223 | color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration]) 224 | ax.legend(loc=2) 225 | ax.set_ylabel('Richness') 226 | ax.set_xlabel('Generations') 227 | plt.show() 228 | savefilename = output_path + "/richness.svg" 229 | rich_fig.savefig(savefilename, bbox_inches='tight') 230 | 231 | ## output CI for the parameters 232 | 233 | summary_fig = plt.figure(figsize=(16, 9)) 234 | ax = summary_fig.add_subplot(111) 235 | 236 | iteration = -1 237 | for k in run_param: 238 | iteration += 1 239 | CI_average = [] 240 | CI_min = [] 241 | CI_max = [] 242 | for t in range(len(output[k][0].fst)): 243 | point_in_time = [] 244 | for n in range(config.reps): 245 | list_of_points = list(output[k][n].fst) 246 | point_in_time.append(list_of_points[t]) 247 | (ave, min, max) = utils.mean_confidence_interval(point_in_time, confidence=0.95) 248 | CI_average.append(ave) 249 | CI_min.append(min) 250 | CI_max.append(max) 251 | ax.plot(list(CI_average), color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration],label='k = %s' % k) 252 | ax.plot(list(CI_min), "--", color="0.5") 253 | ax.plot(list(CI_max), "--", color="0.5") 254 | ax.fill_between(list(CI_average), list(CI_max), list(CI_min), color="None", linestyle="--") 255 | ax.legend(loc=2) 256 | ax.set_ylabel('Fst') 257 | ax.set_xlabel('Generation') 258 | plt.show() 259 | savefilename = output_path + "/summary-ci.svg" 260 | summary_fig.savefig(savefilename, bbox_inches='tight') 261 | 262 | ## now the richness graph 263 | richness_sum_fig = plt.figure(figsize=(16, 9)) 264 | ax = richness_sum_fig.add_subplot(111) 265 | 266 | iteration=-1 267 | for k in run_param: 268 | iteration += 1 269 | CI_average = [] 270 | CI_min = [] 271 | CI_max = [] 272 | for t in range(len(output[k][0].richness)): 273 | point_in_time = [] 274 | for n in range(config.reps): 275 | list_of_points = list(output[k][n].richness) 276 | point_in_time.append(list_of_points[t]) 277 | (ave, min, max) = utils.mean_confidence_interval(point_in_time, confidence=0.95) 278 | CI_average.append(ave) 279 | CI_min.append(min) 280 | CI_max.append(max) 281 | ax.plot(list(CI_average), color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[iteration],label='k = %s' % k) 282 | ax.plot(list(CI_min), "--", color="0.5") 283 | ax.plot(list(CI_max), "--", color="0.5") 284 | ax.fill_between(list(CI_average), list(CI_max), list(CI_min), color="None", linestyle="--") 285 | ax.legend(loc=2) 286 | ax.set_ylabel('Richness') 287 | ax.set_xlabel('Generation') 288 | plt.show() 289 | savefilename = output_path + "/richness-ci.svg" 290 | richness_sum_fig.savefig(savefilename, bbox_inches='tight') 291 | 292 | if __name__ == "__main__": 293 | main() 294 | 295 | -------------------------------------------------------------------------------- /networkdrift/demography/network.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2015. Mark E. Madsen 3 | # 4 | # This work is licensed under the terms of the Apache Software License, Version 2.0. See the file LICENSE for details. 5 | 6 | """ 7 | Description here 8 | 9 | """ 10 | import networkx as nx 11 | import numpy as np 12 | import re 13 | import math 14 | import simuPOP as sim 15 | import logging as log 16 | import matplotlib.pyplot as plt 17 | from matplotlib import colors as mcolors 18 | from itertools import product 19 | import sys 20 | from sklearn.preprocessing import normalize 21 | import networkx as nx 22 | import matplotlib.pyplot as plt 23 | from collections import defaultdict 24 | from itertools import permutations 25 | import operator 26 | import math 27 | import statistics 28 | 29 | class NetworkModel(object): 30 | """ 31 | NetworkModel implements a full "demographic model" in simuPOP terms, 32 | that is, it manages the size and existence of subpopulations, and the 33 | migration matrix between them. The basic data for this model is derived 34 | by the creation of a random small-world NetworkX network in the (watts_strogatz_graph) or 35 | the creation of a network from a GML file. 36 | The network edges represent a set of subpopulations 37 | with unique ID's, and edges between them which are weighted. The 38 | weights may be determined by any underlying model (e.g., distance, 39 | social interaction hierarchy, etc), but need to be interpreted here 40 | purely as the probability of individuals moving between two subpopulations, 41 | since that is the implementation of interaction. 42 | """ 43 | 44 | def __init__(self, 45 | networkmodel="smallworld", 46 | simulation_id=None, 47 | sim_length=1000, 48 | burn_in_time=0, 49 | initial_subpop_size=500, 50 | migrationfraction=0.01, 51 | sub_pops=10, 52 | connectedness=2, 53 | rewiring_prob=0.0, 54 | save_figs=True, 55 | network_iteration=1, 56 | xy=None, 57 | output_path="test"): 58 | """ 59 | :param networkmodel: Name of GML file 60 | :param sim_length: Number of generations to run the simulation 61 | :return: 62 | """ 63 | # BaseMetapopulationModel.__init__(self, numGens = sim_length, initSize = initial_size, infoFields = info_fields, 64 | # ops = ops, sub_pops = num_subpops, connectedness = connectedness, xy=xy rewiring_prob=rewiring_prob, 65 | # save_figs= boolean, iteration=number) 66 | self.networkmodel = networkmodel # default is small world - else GML file location 67 | self.sim_length = sim_length 68 | self.info_fields = 'migrate_to' 69 | self.sim_id = simulation_id 70 | self.burn_in_time = burn_in_time 71 | self.init_subpop_size = initial_subpop_size 72 | self.migration_fraction = migrationfraction 73 | self.connectedness = connectedness # default of 3 74 | self.sub_pops = sub_pops # default of 5 75 | self.rewiring_prob = rewiring_prob # default of 0.0 76 | self._cached_migration_matrix = None 77 | self.subpopulation_names = [] 78 | self.save_figs=save_figs 79 | self.network_iteration=network_iteration 80 | self.xy=[] 81 | self.output_path = output_path 82 | 83 | # Parse the GML files and create a list of NetworkX objects 84 | self._parse_network_model() 85 | 86 | # Determine the initial population configuration 87 | self._calculate_initial_population_configuration() 88 | 89 | # prime the migration matrix 90 | if self.connectedness==self.sub_pops: 91 | self._cached_migration_matrix=self._spatialMigrRates() 92 | log.debug(self._cached_migration_matrix) 93 | self.connectedness=self.sub_pops-1 94 | elif ".gml" in self.networkmodel: 95 | self._cached_migration_matrix=self._calculate_migration_matrix_from_gml() 96 | log.debug(self._cached_migration_matrix) 97 | else: 98 | ## used the fixed migration function for now - which determines each edge 99 | ## note that k * migration rate must be < 1.0 100 | self._cached_migration_matrix = self._calculate_fixed_migration_matrix() 101 | #self._cached_migration_matrix = self._calculate_migration_matrix() 102 | 103 | ############### Private Initialization Methods ############### 104 | 105 | def _parse_network_model(self): 106 | """ 107 | Given a file, read the GML files (format: .gml) 108 | and construct a NetworkX networkmodel from the GML file 109 | """ 110 | if self.networkmodel == "smallworld": 111 | log.debug("Creating small world Watts-Strogatz network with %s nodes and %s connections " % ( 112 | self.sub_pops, self.connectedness)) 113 | k=self.connectedness 114 | if k == self.sub_pops: 115 | k=k-1 116 | network = nx.watts_strogatz_graph(int(self.sub_pops), k, self.rewiring_prob) 117 | self.pos = nx.spring_layout(network, iterations=25) 118 | log.debug("network nodes: %s", '|'.join(sorted(str(list(network.nodes))))) 119 | self.network = network 120 | self.xy=self._set_xy_coordinates() 121 | elif ".gml" in self.networkmodel: 122 | log.debug("Opening GML file %s:", self.networkmodel) 123 | network = nx.read_gml(self.networkmodel) 124 | log.debug("network nodes: %s", '|'.join(sorted(list(network.nodes())))) 125 | self.network = self._create_network_edges_from_k_value(network) 126 | else: 127 | print("There's been a problem - we haven't created the network. Bailing out!\n") 128 | sys.exit() 129 | 130 | if self.save_figs == True: 131 | self.print_graph() 132 | self.save_graph() 133 | 134 | def _calculate_initial_population_configuration(self): 135 | # num subpops is just the number of vertices in the first graph slice. 136 | # first_time = min(self.times) 137 | network = self.network 138 | self.sub_pops = network.number_of_nodes() 139 | log.debug("Number of initial subpopulations: %s", self.sub_pops) 140 | log.debug("list of nodes: %s", list(network.nodes(data=True))) 141 | # subpoplation names - have to switch them to plain strings from unicode or simuPOP won't use them as subpop names 142 | self.subpopulation_names = list(network.nodes) 143 | 144 | log.debug("calc_init_config: subpopulation names: %s", self.subpopulation_names) 145 | 146 | ############### Private Methods for Call() Interface ############### 147 | 148 | def _get_node_label(self, g, id): 149 | return g.node[id]["label"].encode('utf-8', 'ignore') 150 | 151 | def _get_id_for_subpop_name(self, pop, name): 152 | return pop.subPopByName(name) 153 | 154 | def _get_node_parent(self, g, id): 155 | return g.node[id]["parent_node"].encode('utf-8', 'ignore') 156 | 157 | def _get_subpop_idname_map(self, pop): 158 | names = pop.subPopNames() 159 | name_id_map = dict() 160 | for name in names: 161 | id = pop.subPopByName(name) 162 | name_id_map[id] = name 163 | return name_id_map 164 | 165 | def _calculate_fixed_migration_matrix(self): 166 | for (node1, node2, data) in self.network.edges(data=True): 167 | self.network.add_edge(node1, node2, weight=self.migration_fraction) 168 | g_mat = nx.to_numpy_matrix(self.network) 169 | #print("normed_matrix: ", g_mat) 170 | return g_mat.tolist() 171 | 172 | def _calculate_migration_matrix_from_gml(self): 173 | g_mat = nx.to_numpy_matrix(self.network).astype(np.float) 174 | return g_mat.tolist() 175 | 176 | def _calculate_migration_matrix(self): 177 | g_mat = nx.to_numpy_matrix(self.network, weight=self.migration_fraction).astype(np.float) 178 | #print("g_mat: ", g_mat) 179 | # get the column totals 180 | rtot = np.sum(g_mat, axis=1) 181 | scaled = (g_mat / rtot) * self.migration_fraction 182 | diag = np.eye(np.shape(g_mat)[0]) * (1.0 - self.migration_fraction) 183 | g_mat_scaled = diag + scaled 184 | log.debug("scaled migration matrix: %s", g_mat_scaled.tolist()) 185 | #print("g_mat_scaled: ", g_mat_scaled) 186 | return g_mat_scaled.tolist() 187 | 188 | def _spatialMigrRates(self): 189 | ''' 190 | Return a migration matrix where migration rates between two 191 | subpopulations vary according to Euclidean distance between them. 192 | 193 | xy 194 | A list of (x,y) location for each subpopulation. 195 | 196 | r 197 | Migrate rate between two subpopulations is exp(-r*d_ij) where 198 | d_ij is the Euclidean distance between subpopulations i and j. 199 | ''' 200 | r=self.migration_fraction 201 | xy=list(self.xy) 202 | #print(xy) 203 | nSubPop = self.sub_pops 204 | rate = [] 205 | for i in range(nSubPop): 206 | rate.append([]) 207 | for j in range(nSubPop): 208 | if i == j: 209 | rate[-1].append(0) 210 | continue 211 | d_ij = math.sqrt((xy[i][0] - xy[j][0]) ** 2 + (xy[i][1] - xy[j][1]) ** 2) 212 | rate[-1].append(math.exp(-1 * r * d_ij)) 213 | self._cached_migration_matrix=rate 214 | return rate 215 | 216 | def _set_xy_coordinates(self): 217 | radius=100 218 | pts=self.sub_pops 219 | for x, y in product(range(0, int(radius) + 1, int(360 / pts)), repeat=2): 220 | if x ** 2 + y ** 2 <= radius ** 2: 221 | yield from set(((x, y), (x, -y), (-x, y), (-x, -y),)) 222 | 223 | def _create_network_edges_from_k_value(self,network): 224 | new_network=nx.Graph() 225 | nearest_neighbor_distance = [] 226 | number = 0 227 | node_pos = {} 228 | node_name = {} 229 | ## first build a list of locations and names for the new nodes 230 | for (num, data) in network.nodes(data=True): 231 | node_pos[num] = data['pos'] 232 | node_name[num] = data['name'] 233 | 234 | for num, xy in list(node_pos.items()): 235 | new_network.add_node(num, x=xy[0], y=xy[1], pos=(xy[0], xy[1]), name=node_name[num]) 236 | 237 | self.pos = nx.get_node_attributes(new_network, 'pos') 238 | 239 | for num, xy in list(node_pos.items()): 240 | x1, y1 = xy 241 | # print("working on node %s" % num) 242 | node_distances = [] 243 | sorted_node_distances = [] 244 | # now iterate through to find the n closest networks 245 | ccount = 0 246 | for (num2, xy2) in list(node_pos.items()): 247 | x2, y2 = xy2 248 | if num != num2: 249 | distance = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) 250 | node_distances.append(distance) 251 | 252 | sorted_node_distances.append(min(node_distances)) 253 | smallest_distance = min(sorted_node_distances) 254 | nearest_neighbor_distance.append(smallest_distance) 255 | 256 | mean_nearest_neighbor_distance = statistics.mean(nearest_neighbor_distance) 257 | 258 | # now add the connections based on the degree of k specified 259 | for num,(x,y) in list(node_pos.items()): 260 | # print("working on node %s" % num) 261 | node_distances = {} 262 | # now iterate through to find the n closes networks 263 | ccount = 0 264 | for (num2, (x2,y2)) in list(node_pos.items()): 265 | if num != num2: 266 | node_distances[num2] = (math.sqrt((x - x2) ** 2 + (y - y2) ** 2)) 267 | sorted_node_distances = sorted(node_distances.items(), key=operator.itemgetter(1)) 268 | list_of_edges_to_add = list(range(0, self.connectedness)) 269 | current_k = self.connectedness 270 | # note: we dont want to add edges if they are already there 271 | for e in list_of_edges_to_add: 272 | ne, dist = sorted_node_distances[e] 273 | # network.add_edge(node_locations[num],node_locations[ne], weight=dist ) 274 | weight=(dist / mean_nearest_neighbor_distance) * self.migration_fraction 275 | new_network.add_edge(num, ne, weight=weight) 276 | 277 | return new_network 278 | 279 | ###################### Public API ##################### 280 | 281 | def get_info_fields(self): 282 | return self.info_fields 283 | 284 | def get_connectedness(self): 285 | return self.connectedness 286 | 287 | def get_initial_size(self): 288 | return [self.init_subpop_size] * self.sub_pops 289 | 290 | def get_subpopulation_names(self): 291 | return str(list(self.subpopulation_names)) 292 | 293 | def get_subpopulation_sizes(self): 294 | return self.subpop_sizes 295 | 296 | def get_subpopulation_number(self): 297 | return len(self.subpopulation_names) 298 | 299 | def get_migration_matrix(self): 300 | return self._cached_migration_matrix 301 | 302 | def print_graph(self): 303 | """ 304 | Show us the graph of the network. 305 | :return: nothing - should be a matplotlib plot 306 | """ 307 | plt.subplot(111) 308 | nx.draw(self.network,self.pos,node_color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[self.network_iteration], with_labels=True, font_weight='bold') 309 | plt.show() 310 | 311 | def save_graph(self): 312 | """ 313 | Save the graph of the network. 314 | :return: nothing - should be saved file 315 | """ 316 | name = "%s/k-%s.svg" % (self.output_path,self.connectedness) 317 | nx.draw(self.network,self.pos,node_color=list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys())[self.network_iteration], with_labels=True, font_weight='bold') 318 | plt.savefig(name) 319 | 320 | def __call__(self, pop): 321 | """ 322 | Main public interface to this demography model. When the model object is called in every time step, 323 | this method creates a new migration matrix. 324 | 325 | After migration, the stat function is called to inventory the subpopulation sizes, which are then 326 | returned since they're handed to the RandomSelection mating operator. 327 | 328 | If a new network slice is not active, the migration matrix from the previous step is applied again, 329 | and the new subpopulation sizes are returns to the RandomSelection mating operator as before. 330 | 331 | :return: A list of the subpopulation sizes for each subpopulation 332 | """ 333 | if 'gen' not in pop.vars(): 334 | gen = 0 335 | else: 336 | gen = pop.dvars().gen 337 | 338 | ######### Do the per tick processing ########## 339 | 340 | log.debug("========= Processing network =============") 341 | # self._dbg_slice_pop_start(pop,gen) 342 | 343 | # update the migration matrix 344 | self._cached_migration_matrix = self._calculate_migration_matrix(gen) 345 | 346 | sim.migrate(pop, self._cached_migration_matrix) 347 | sim.stat(pop, popSize=True) 348 | # cache the new subpopulation names and sizes for debug and logging purposes 349 | # before returning them to the calling function 350 | self.subpopulation_names = sorted(str(list(pop.subPopNames()))) 351 | self.subpop_sizes = pop.subPopSizes() 352 | #print(self.subpop_sizes) 353 | return pop.subPopSizes() 354 | 355 | -------------------------------------------------------------------------------- /testdata/test-network.gml: -------------------------------------------------------------------------------- 1 | graph [ 2 | name "(complete_graph(8))_with_int_labels" 3 | node [ 4 | id 0 5 | label "assemblage-167-400" 6 | appears_in_slice "1" 7 | ycoord "400" 8 | level "None" 9 | cluster_id "0" 10 | xcoord "167" 11 | parent_node "initial" 12 | ] 13 | node [ 14 | id 1 15 | label "assemblage-176-392" 16 | appears_in_slice "1" 17 | ycoord "392" 18 | level "None" 19 | cluster_id "0" 20 | xcoord "176" 21 | parent_node "initial" 22 | ] 23 | node [ 24 | id 2 25 | label "assemblage-165-402" 26 | appears_in_slice "1" 27 | ycoord "402" 28 | level "None" 29 | cluster_id "0" 30 | xcoord "165" 31 | parent_node "initial" 32 | ] 33 | node [ 34 | id 3 35 | label "assemblage-172-374" 36 | appears_in_slice "1" 37 | ycoord "374" 38 | level "None" 39 | cluster_id "0" 40 | xcoord "172" 41 | parent_node "initial" 42 | ] 43 | node [ 44 | id 4 45 | label "assemblage-182-393" 46 | appears_in_slice "1" 47 | ycoord "393" 48 | level "None" 49 | cluster_id "0" 50 | xcoord "182" 51 | parent_node "initial" 52 | ] 53 | node [ 54 | id 5 55 | label "assemblage-164-395" 56 | appears_in_slice "1" 57 | ycoord "395" 58 | level "None" 59 | cluster_id "0" 60 | xcoord "164" 61 | parent_node "initial" 62 | ] 63 | node [ 64 | id 6 65 | label "assemblage-162-396" 66 | appears_in_slice "1" 67 | ycoord "396" 68 | level "None" 69 | cluster_id "0" 70 | xcoord "162" 71 | parent_node "initial" 72 | ] 73 | node [ 74 | id 7 75 | label "assemblage-185-383" 76 | appears_in_slice "1" 77 | ycoord "383" 78 | level "None" 79 | cluster_id "0" 80 | xcoord "185" 81 | parent_node "initial" 82 | ] 83 | node [ 84 | id 8 85 | label "assemblage-396-320" 86 | appears_in_slice "1" 87 | ycoord "320" 88 | level "None" 89 | cluster_id "1" 90 | xcoord "396" 91 | parent_node "initial" 92 | ] 93 | node [ 94 | id 9 95 | label "assemblage-398-306" 96 | appears_in_slice "1" 97 | ycoord "306" 98 | level "None" 99 | cluster_id "1" 100 | xcoord "398" 101 | parent_node "initial" 102 | ] 103 | node [ 104 | id 10 105 | label "assemblage-411-290" 106 | appears_in_slice "1" 107 | ycoord "290" 108 | level "None" 109 | cluster_id "1" 110 | xcoord "411" 111 | parent_node "initial" 112 | ] 113 | node [ 114 | id 11 115 | label "assemblage-404-314" 116 | appears_in_slice "1" 117 | ycoord "314" 118 | level "None" 119 | cluster_id "1" 120 | xcoord "404" 121 | parent_node "initial" 122 | ] 123 | node [ 124 | id 12 125 | label "assemblage-422-296" 126 | appears_in_slice "1" 127 | ycoord "296" 128 | level "None" 129 | cluster_id "1" 130 | xcoord "422" 131 | parent_node "initial" 132 | ] 133 | node [ 134 | id 13 135 | label "assemblage-401-292" 136 | appears_in_slice "1" 137 | ycoord "292" 138 | level "None" 139 | cluster_id "1" 140 | xcoord "401" 141 | parent_node "initial" 142 | ] 143 | node [ 144 | id 14 145 | label "assemblage-418-297" 146 | appears_in_slice "1" 147 | ycoord "297" 148 | level "None" 149 | cluster_id "1" 150 | xcoord "418" 151 | parent_node "initial" 152 | ] 153 | node [ 154 | id 15 155 | label "assemblage-419-328" 156 | appears_in_slice "1" 157 | ycoord "328" 158 | level "None" 159 | cluster_id "1" 160 | xcoord "419" 161 | parent_node "initial" 162 | ] 163 | node [ 164 | id 16 165 | label "assemblage-97-446" 166 | appears_in_slice "1" 167 | ycoord "446" 168 | level "None" 169 | cluster_id "2" 170 | xcoord "97" 171 | parent_node "initial" 172 | ] 173 | node [ 174 | id 17 175 | label "assemblage-74-450" 176 | appears_in_slice "1" 177 | ycoord "450" 178 | level "None" 179 | cluster_id "2" 180 | xcoord "74" 181 | parent_node "initial" 182 | ] 183 | node [ 184 | id 18 185 | label "assemblage-87-458" 186 | appears_in_slice "1" 187 | ycoord "458" 188 | level "None" 189 | cluster_id "2" 190 | xcoord "87" 191 | parent_node "initial" 192 | ] 193 | node [ 194 | id 19 195 | label "assemblage-82-436" 196 | appears_in_slice "1" 197 | ycoord "436" 198 | level "None" 199 | cluster_id "2" 200 | xcoord "82" 201 | parent_node "initial" 202 | ] 203 | node [ 204 | id 20 205 | label "assemblage-92-441" 206 | appears_in_slice "1" 207 | ycoord "441" 208 | level "None" 209 | cluster_id "2" 210 | xcoord "92" 211 | parent_node "initial" 212 | ] 213 | node [ 214 | id 21 215 | label "assemblage-108-430" 216 | appears_in_slice "1" 217 | ycoord "430" 218 | level "None" 219 | cluster_id "2" 220 | xcoord "108" 221 | parent_node "initial" 222 | ] 223 | node [ 224 | id 22 225 | label "assemblage-89-472" 226 | appears_in_slice "1" 227 | ycoord "472" 228 | level "None" 229 | cluster_id "2" 230 | xcoord "89" 231 | parent_node "initial" 232 | ] 233 | node [ 234 | id 23 235 | label "assemblage-87-456" 236 | appears_in_slice "1" 237 | ycoord "456" 238 | level "None" 239 | cluster_id "2" 240 | xcoord "87" 241 | parent_node "initial" 242 | ] 243 | edge [ 244 | source 0 245 | target 1 246 | distance 4.12310562562 247 | normalized_weight 0.5 248 | unnormalized_weight 0.5 249 | weight 0.5 250 | ] 251 | edge [ 252 | source 0 253 | target 2 254 | distance 2.0 255 | normalized_weight 0.5 256 | unnormalized_weight 0.5 257 | weight 0.5 258 | ] 259 | edge [ 260 | source 0 261 | target 3 262 | distance 5.56776436283 263 | normalized_weight 0.5 264 | unnormalized_weight 0.5 265 | weight 0.5 266 | ] 267 | edge [ 268 | source 0 269 | target 4 270 | distance 4.69041575982 271 | normalized_weight 0.5 272 | unnormalized_weight 0.5 273 | weight 0.5 274 | ] 275 | edge [ 276 | source 0 277 | target 5 278 | distance 2.82842712475 279 | normalized_weight 0.5 280 | unnormalized_weight 0.5 281 | weight 0.5 282 | ] 283 | edge [ 284 | source 0 285 | target 6 286 | distance 3.0 287 | normalized_weight 0.5 288 | unnormalized_weight 0.5 289 | weight 0.5 290 | ] 291 | edge [ 292 | source 0 293 | target 7 294 | distance 5.9160797831 295 | normalized_weight 0.5 296 | unnormalized_weight 0.5 297 | weight 0.5 298 | ] 299 | edge [ 300 | source 0 301 | target 9 302 | distance 18.0277563773 303 | ] 304 | edge [ 305 | source 1 306 | target 2 307 | distance 4.58257569496 308 | normalized_weight 0.5 309 | unnormalized_weight 0.5 310 | weight 0.5 311 | ] 312 | edge [ 313 | source 1 314 | target 3 315 | distance 4.69041575982 316 | normalized_weight 0.5 317 | unnormalized_weight 0.5 318 | weight 0.5 319 | ] 320 | edge [ 321 | source 1 322 | target 4 323 | distance 2.64575131106 324 | normalized_weight 0.5 325 | unnormalized_weight 0.5 326 | weight 0.5 327 | ] 328 | edge [ 329 | source 1 330 | target 5 331 | distance 3.87298334621 332 | normalized_weight 0.5 333 | unnormalized_weight 0.5 334 | weight 0.5 335 | ] 336 | edge [ 337 | source 1 338 | target 6 339 | distance 4.24264068712 340 | normalized_weight 0.5 341 | unnormalized_weight 0.5 342 | weight 0.5 343 | ] 344 | edge [ 345 | source 1 346 | target 7 347 | distance 4.24264068712 348 | normalized_weight 0.5 349 | unnormalized_weight 0.5 350 | weight 0.5 351 | ] 352 | edge [ 353 | source 1 354 | target 22 355 | distance 12.9228479833 356 | ] 357 | edge [ 358 | source 2 359 | target 3 360 | distance 5.9160797831 361 | normalized_weight 0.5 362 | unnormalized_weight 0.5 363 | weight 0.5 364 | ] 365 | edge [ 366 | source 2 367 | target 4 368 | distance 5.09901951359 369 | normalized_weight 0.5 370 | unnormalized_weight 0.5 371 | weight 0.5 372 | ] 373 | edge [ 374 | source 2 375 | target 5 376 | distance 2.82842712475 377 | normalized_weight 0.5 378 | unnormalized_weight 0.5 379 | weight 0.5 380 | ] 381 | edge [ 382 | source 2 383 | target 6 384 | distance 3.0 385 | normalized_weight 0.5 386 | unnormalized_weight 0.5 387 | weight 0.5 388 | ] 389 | edge [ 390 | source 2 391 | target 7 392 | distance 6.2449979984 393 | normalized_weight 0.5 394 | unnormalized_weight 0.5 395 | weight 0.5 396 | ] 397 | edge [ 398 | source 2 399 | target 15 400 | distance 18.1107702763 401 | ] 402 | edge [ 403 | source 2 404 | target 16 405 | distance 10.5830052443 406 | ] 407 | edge [ 408 | source 3 409 | target 4 410 | distance 5.38516480713 411 | normalized_weight 0.5 412 | unnormalized_weight 0.5 413 | weight 0.5 414 | ] 415 | edge [ 416 | source 3 417 | target 5 418 | distance 5.38516480713 419 | normalized_weight 0.5 420 | unnormalized_weight 0.5 421 | weight 0.5 422 | ] 423 | edge [ 424 | source 3 425 | target 6 426 | distance 5.65685424949 427 | normalized_weight 0.5 428 | unnormalized_weight 0.5 429 | weight 0.5 430 | ] 431 | edge [ 432 | source 3 433 | target 7 434 | distance 4.69041575982 435 | normalized_weight 0.5 436 | unnormalized_weight 0.5 437 | weight 0.5 438 | ] 439 | edge [ 440 | source 4 441 | target 5 442 | distance 4.472135955 443 | normalized_weight 0.5 444 | unnormalized_weight 0.5 445 | weight 0.5 446 | ] 447 | edge [ 448 | source 4 449 | target 6 450 | distance 4.79583152331 451 | normalized_weight 0.5 452 | unnormalized_weight 0.5 453 | weight 0.5 454 | ] 455 | edge [ 456 | source 4 457 | target 7 458 | distance 3.60555127546 459 | normalized_weight 0.5 460 | unnormalized_weight 0.5 461 | weight 0.5 462 | ] 463 | edge [ 464 | source 5 465 | target 6 466 | distance 1.73205080757 467 | normalized_weight 0.5 468 | unnormalized_weight 0.5 469 | weight 0.5 470 | ] 471 | edge [ 472 | source 5 473 | target 7 474 | distance 5.74456264654 475 | normalized_weight 0.5 476 | unnormalized_weight 0.5 477 | weight 0.5 478 | ] 479 | edge [ 480 | source 5 481 | target 10 482 | distance 18.7616630393 483 | ] 484 | edge [ 485 | source 6 486 | target 7 487 | distance 6.0 488 | normalized_weight 0.5 489 | unnormalized_weight 0.5 490 | weight 0.5 491 | ] 492 | edge [ 493 | source 7 494 | target 17 495 | distance 13.3416640641 496 | ] 497 | edge [ 498 | source 8 499 | target 9 500 | distance 4.0 501 | normalized_weight 0.5 502 | unnormalized_weight 0.5 503 | weight 0.5 504 | ] 505 | edge [ 506 | source 8 507 | target 10 508 | distance 6.7082039325 509 | normalized_weight 0.5 510 | unnormalized_weight 0.5 511 | weight 0.5 512 | ] 513 | edge [ 514 | source 8 515 | target 11 516 | distance 3.74165738677 517 | normalized_weight 0.5 518 | unnormalized_weight 0.5 519 | weight 0.5 520 | ] 521 | edge [ 522 | source 8 523 | target 12 524 | distance 7.07106781187 525 | normalized_weight 0.5 526 | unnormalized_weight 0.5 527 | weight 0.5 528 | ] 529 | edge [ 530 | source 8 531 | target 13 532 | distance 5.74456264654 533 | normalized_weight 0.5 534 | unnormalized_weight 0.5 535 | weight 0.5 536 | ] 537 | edge [ 538 | source 8 539 | target 14 540 | distance 6.7082039325 541 | normalized_weight 0.5 542 | unnormalized_weight 0.5 543 | weight 0.5 544 | ] 545 | edge [ 546 | source 8 547 | target 15 548 | distance 5.56776436283 549 | normalized_weight 0.5 550 | unnormalized_weight 0.5 551 | weight 0.5 552 | ] 553 | edge [ 554 | source 9 555 | target 10 556 | distance 5.38516480713 557 | normalized_weight 0.5 558 | unnormalized_weight 0.5 559 | weight 0.5 560 | ] 561 | edge [ 562 | source 9 563 | target 11 564 | distance 3.74165738677 565 | normalized_weight 0.5 566 | unnormalized_weight 0.5 567 | weight 0.5 568 | ] 569 | edge [ 570 | source 9 571 | target 12 572 | distance 5.83095189485 573 | normalized_weight 0.5 574 | unnormalized_weight 0.5 575 | weight 0.5 576 | ] 577 | edge [ 578 | source 9 579 | target 13 580 | distance 4.12310562562 581 | normalized_weight 0.5 582 | unnormalized_weight 0.5 583 | weight 0.5 584 | ] 585 | edge [ 586 | source 9 587 | target 14 588 | distance 5.38516480713 589 | normalized_weight 0.5 590 | unnormalized_weight 0.5 591 | weight 0.5 592 | ] 593 | edge [ 594 | source 9 595 | target 15 596 | distance 6.5574385243 597 | normalized_weight 0.5 598 | unnormalized_weight 0.5 599 | weight 0.5 600 | ] 601 | edge [ 602 | source 10 603 | target 11 604 | distance 5.56776436283 605 | normalized_weight 0.5 606 | unnormalized_weight 0.5 607 | weight 0.5 608 | ] 609 | edge [ 610 | source 10 611 | target 12 612 | distance 4.12310562562 613 | normalized_weight 0.5 614 | unnormalized_weight 0.5 615 | weight 0.5 616 | ] 617 | edge [ 618 | source 10 619 | target 13 620 | distance 3.46410161514 621 | normalized_weight 0.5 622 | unnormalized_weight 0.5 623 | weight 0.5 624 | ] 625 | edge [ 626 | source 10 627 | target 14 628 | distance 3.74165738677 629 | normalized_weight 0.5 630 | unnormalized_weight 0.5 631 | weight 0.5 632 | ] 633 | edge [ 634 | source 10 635 | target 15 636 | distance 6.78232998313 637 | normalized_weight 0.5 638 | unnormalized_weight 0.5 639 | weight 0.5 640 | ] 641 | edge [ 642 | source 10 643 | target 21 644 | distance 21.0475651798 645 | ] 646 | edge [ 647 | source 11 648 | target 12 649 | distance 6.0 650 | normalized_weight 0.5 651 | unnormalized_weight 0.5 652 | weight 0.5 653 | ] 654 | edge [ 655 | source 11 656 | target 13 657 | distance 5.0 658 | normalized_weight 0.5 659 | unnormalized_weight 0.5 660 | weight 0.5 661 | ] 662 | edge [ 663 | source 11 664 | target 14 665 | distance 5.56776436283 666 | normalized_weight 0.5 667 | unnormalized_weight 0.5 668 | weight 0.5 669 | ] 670 | edge [ 671 | source 11 672 | target 15 673 | distance 5.38516480713 674 | normalized_weight 0.5 675 | unnormalized_weight 0.5 676 | weight 0.5 677 | ] 678 | edge [ 679 | source 12 680 | target 13 681 | distance 5.0 682 | normalized_weight 0.5 683 | unnormalized_weight 0.5 684 | weight 0.5 685 | ] 686 | edge [ 687 | source 12 688 | target 14 689 | distance 2.2360679775 690 | normalized_weight 0.5 691 | unnormalized_weight 0.5 692 | weight 0.5 693 | ] 694 | edge [ 695 | source 12 696 | target 15 697 | distance 5.9160797831 698 | normalized_weight 0.5 699 | unnormalized_weight 0.5 700 | weight 0.5 701 | ] 702 | edge [ 703 | source 12 704 | target 17 705 | distance 22.4053565024 706 | ] 707 | edge [ 708 | source 13 709 | target 14 710 | distance 4.69041575982 711 | normalized_weight 0.5 712 | unnormalized_weight 0.5 713 | weight 0.5 714 | ] 715 | edge [ 716 | source 13 717 | target 15 718 | distance 7.34846922835 719 | normalized_weight 0.5 720 | unnormalized_weight 0.5 721 | weight 0.5 722 | ] 723 | edge [ 724 | source 13 725 | target 19 726 | distance 21.5174347914 727 | ] 728 | edge [ 729 | source 14 730 | target 15 731 | distance 5.65685424949 732 | normalized_weight 0.5 733 | unnormalized_weight 0.5 734 | weight 0.5 735 | ] 736 | edge [ 737 | source 16 738 | target 17 739 | distance 5.19615242271 740 | normalized_weight 0.5 741 | weight 0.5 742 | unnormalized_weight 0.5 743 | ] 744 | edge [ 745 | source 16 746 | target 18 747 | distance 4.69041575982 748 | normalized_weight 0.5 749 | weight 0.5 750 | unnormalized_weight 0.5 751 | ] 752 | edge [ 753 | source 16 754 | target 19 755 | distance 5.0 756 | normalized_weight 0.5 757 | weight 0.5 758 | unnormalized_weight 0.5 759 | ] 760 | edge [ 761 | source 16 762 | target 20 763 | distance 3.16227766017 764 | normalized_weight 0.5 765 | weight 0.5 766 | unnormalized_weight 0.5 767 | ] 768 | edge [ 769 | source 16 770 | target 21 771 | distance 5.19615242271 772 | normalized_weight 0.5 773 | weight 0.5 774 | unnormalized_weight 0.5 775 | ] 776 | edge [ 777 | source 16 778 | target 22 779 | distance 5.83095189485 780 | normalized_weight 0.5 781 | weight 0.5 782 | unnormalized_weight 0.5 783 | ] 784 | edge [ 785 | source 16 786 | target 23 787 | distance 4.472135955 788 | normalized_weight 0.5 789 | weight 0.5 790 | unnormalized_weight 0.5 791 | ] 792 | edge [ 793 | source 17 794 | target 18 795 | distance 4.58257569496 796 | normalized_weight 0.5 797 | weight 0.5 798 | unnormalized_weight 0.5 799 | ] 800 | edge [ 801 | source 17 802 | target 19 803 | distance 4.69041575982 804 | normalized_weight 0.5 805 | weight 0.5 806 | unnormalized_weight 0.5 807 | ] 808 | edge [ 809 | source 17 810 | target 20 811 | distance 5.19615242271 812 | normalized_weight 0.5 813 | weight 0.5 814 | unnormalized_weight 0.5 815 | ] 816 | edge [ 817 | source 17 818 | target 21 819 | distance 7.34846922835 820 | normalized_weight 0.5 821 | weight 0.5 822 | unnormalized_weight 0.5 823 | ] 824 | edge [ 825 | source 17 826 | target 22 827 | distance 6.0827625303 828 | normalized_weight 0.5 829 | weight 0.5 830 | unnormalized_weight 0.5 831 | ] 832 | edge [ 833 | source 17 834 | target 23 835 | distance 4.35889894354 836 | normalized_weight 0.5 837 | weight 0.5 838 | unnormalized_weight 0.5 839 | ] 840 | edge [ 841 | source 18 842 | target 19 843 | distance 5.19615242271 844 | normalized_weight 0.5 845 | weight 0.5 846 | unnormalized_weight 0.5 847 | ] 848 | edge [ 849 | source 18 850 | target 20 851 | distance 4.69041575982 852 | normalized_weight 0.5 853 | weight 0.5 854 | unnormalized_weight 0.5 855 | ] 856 | edge [ 857 | source 18 858 | target 21 859 | distance 7.0 860 | normalized_weight 0.5 861 | weight 0.5 862 | unnormalized_weight 0.5 863 | ] 864 | edge [ 865 | source 18 866 | target 22 867 | distance 4.0 868 | normalized_weight 0.5 869 | weight 0.5 870 | unnormalized_weight 0.5 871 | ] 872 | edge [ 873 | source 18 874 | target 23 875 | distance 1.41421356237 876 | normalized_weight 0.5 877 | weight 0.5 878 | unnormalized_weight 0.5 879 | ] 880 | edge [ 881 | source 19 882 | target 20 883 | distance 3.87298334621 884 | normalized_weight 0.5 885 | weight 0.5 886 | unnormalized_weight 0.5 887 | ] 888 | edge [ 889 | source 19 890 | target 21 891 | distance 5.65685424949 892 | normalized_weight 0.5 893 | weight 0.5 894 | unnormalized_weight 0.5 895 | ] 896 | edge [ 897 | source 19 898 | target 22 899 | distance 6.5574385243 900 | normalized_weight 0.5 901 | weight 0.5 902 | unnormalized_weight 0.5 903 | ] 904 | edge [ 905 | source 19 906 | target 23 907 | distance 5.0 908 | normalized_weight 0.5 909 | weight 0.5 910 | unnormalized_weight 0.5 911 | ] 912 | edge [ 913 | source 20 914 | target 21 915 | distance 5.19615242271 916 | normalized_weight 0.5 917 | weight 0.5 918 | unnormalized_weight 0.5 919 | ] 920 | edge [ 921 | source 20 922 | target 22 923 | distance 5.83095189485 924 | normalized_weight 0.5 925 | weight 0.5 926 | unnormalized_weight 0.5 927 | ] 928 | edge [ 929 | source 20 930 | target 23 931 | distance 4.472135955 932 | normalized_weight 0.5 933 | weight 0.5 934 | unnormalized_weight 0.5 935 | ] 936 | edge [ 937 | source 21 938 | target 22 939 | distance 7.81024967591 940 | normalized_weight 0.5 941 | weight 0.5 942 | unnormalized_weight 0.5 943 | ] 944 | edge [ 945 | source 21 946 | target 23 947 | distance 6.8556546004 948 | normalized_weight 0.5 949 | weight 0.5 950 | unnormalized_weight 0.5 951 | ] 952 | edge [ 953 | source 22 954 | target 23 955 | distance 4.24264068712 956 | normalized_weight 0.5 957 | weight 0.5 958 | unnormalized_weight 0.5 959 | ] 960 | ] 961 | --------------------------------------------------------------------------------