InstituteforDiseaseModeling / covasim

COVID-19 Agent-based Simulator (Covasim): a model for exploring coronavirus dynamics and interventions
https://covasim.org
MIT License
250 stars 223 forks source link

TypeError: 'float' object cannot be interpreted as an integer arises if pop_size is a float #309

Closed pausz closed 3 years ago

pausz commented 3 years ago

Describe the bug

TypeError: 'float' object cannot be interpreted as an integer arises if pop_size is a float (eg, when expressed as 10e3), though the error depends on where pop_size was defined as a float rather than integer.

This was the original stack was

  Covasim 3.0.1 (2021-04-16) — © 2021 by IDM
    Automatically adding cumulative column cum_tests_raw from new_tests_raw
  Loading population from /home/paula/Work/Code/Python/covasim-qld-vaccine/qld-model/inputs/qldppl-2021.pop
  Traceback (most recent call last):
    File "run_qld_vaccine_rollout.py", line 365, in <module>
      sim  = make_sim(load_pop=True, 
    File "run_qld_vaccine_rollout.py", line 209, in make_sim
      sim = cv.Sim(pars=pars,
    File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 81, in __init__
      self.load_population(popfile)  # Load the population, if provided
    File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 390, in load_population
      self.people.validate() # Internal consistency check
    File "/home/paula/Work/Code/Python/covasim/covasim/base.py", line 1037, in validate
      expected_len = len(self)
  TypeError: 'float' object cannot be interpreted as an integer

To reproduce

Case A: pop_size=200e3 in make_save_people() and sim_pars

import covasim as cv
import sciris as sc

def make_save_people(pop_size=200e3, pop_infected=0, popfilename='mypop.pop'):

    ppl_pars = {'pop_size': pop_size, 
                'pop_infected': pop_infected}

    people = cv.People(pars=ppl_pars)

    # Save object
    sc.saveobj(popfilename, people)
    return

popfilename='mypop-float.pop'
make_save_people(popfilename=popfilename)

sim_pars = dict(
    pop_size = 200e3, 
)

sim = cv.Sim(pars=sim_pars,
             popfile=popfilename,
             load_pop=True)
sim.initialize() 
sim.run()

Stack:

Covasim 3.0.1 (2021-04-16) — © 2021 by IDM
Loading population from /home/paula/Work/Code/Python/covasim-qld-vaccine/qld-model/mypop-float.pop
Traceback (most recent call last):
  File "population_int_float_clash.py", line 30, in <module>
    sim = cv.Sim(pars=sim_pars,
  File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 81, in __init__
    self.load_population(popfile)  # Load the population, if provided
  File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 390, in load_population
    self.people.validate() # Internal consistency check
  File "/home/paula/Work/Code/Python/covasim/covasim/base.py", line 1037, in validate
    expected_len = len(self)
TypeError: 'float' object cannot be interpreted as an integer

Case B: pop_size=200000 in make_save_people() and pop_size=200e3 in sim_pars

import covasim as cv
import sciris as sc

def make_save_people(pop_size=200000, pop_infected=0, popfilename='mypop.pop'):

    ppl_pars = {'pop_size': pop_size, 
                'pop_infected': pop_infected}

    people = cv.People(pars=ppl_pars)

    # Save object
    sc.saveobj(popfilename, people)
    return

popfilename='mypop-float.pop'
make_save_people(popfilename=popfilename)

sim_pars = dict(
    pop_size = 200e3, 
)

sim = cv.Sim(pars=sim_pars,
             popfile=popfilename,
             load_pop=True)
sim.initialize() 
sim.run()

Stack:

Covasim 3.0.1 (2021-04-16) — © 2021 by IDM
Loading population from /home/paula/Work/Code/Python/covasim-qld-vaccine/qld-model/mypop-float.pop
Traceback (most recent call last):
  File "population_int_float_clash.py", line 30, in <module>
    sim = cv.Sim(pars=sim_pars,
  File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 81, in __init__
    self.load_population(popfile)  # Load the population, if provided
  File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 390, in load_population
    self.people.validate() # Internal consistency check
  File "/home/paula/Work/Code/Python/covasim/covasim/base.py", line 1037, in validate
    expected_len = len(self)
TypeError: 'float' object cannot be interpreted as an integer

Case C: pop_size=200e3 in make_save_people() and pop_size=200000 in sim_pars

def make_save_people(pop_size=200e3, pop_infected=0, popfilename='mypop.pop'):

    ppl_pars = {'pop_size': pop_size, 
                'pop_infected': pop_infected}

    people = cv.People(pars=ppl_pars)

    # Save object
    sc.saveobj(popfilename, people)
    return

popfilename='mypop-float.pop'
make_save_people(popfilename=popfilename)

sim_pars = dict(
    pop_size = 200000, 
)

sim = cv.Sim(pars=sim_pars,
             popfile=popfilename,
             load_pop=True)
sim.initialize() 
sim.run()

Stack:

Covasim 3.0.1 (2021-04-16) — © 2021 by IDM
Loading population from /home/paula/Work/Code/Python/covasim-qld-vaccine/qld-model/mypop-float.pop
Traceback (most recent call last):
  File "population_int_float_clash.py", line 33, in <module>
    sim.initialize() 
  File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 109, in initialize
    self.validate_pars() # Ensure parameters have valid values
  File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 263, in validate_pars
    self.validate_layer_pars()
  File "/home/paula/Work/Code/Python/covasim/covasim/sim.py", line 187, in validate_layer_pars
    raise sc.KeyNotFoundError(errormsg)
sciris.sc_utils.KeyNotFoundError: Please update your parameter keys ['a'] to match population keys set(). You may find sim.reset_layer_pars() helpful.

Case D: pop_size=200000 in make_save_people() and sim_pars

No errors are produced

Expected behavior

No errors whether pop_size is defined by users as a (round) float or integer number, anywhere in the code. For instance, the following snippets of code work without errors despite pop_size being a float:

ppl = cv.People(200e3)

or

import covasim as cv
pars = dict(
    pop_size = 200e3, 
    pop_type = 'hybrid',
)

sim = cv.Sim(pars)
sim.initialize() # Create people
fig = sim.people.plot() # Show statistics of the people

Platform:

pausz commented 3 years ago

environment yaml file in txt format cause github doesn't recognise .yaml for attachments

pycovasim3-dev.txt

cliffckerr commented 3 years ago

Thanks @pausz ! So this is a couple things, a bug and a poor error message :) Actually, none of these should work since the population isn't being created with enough information (specifically, contact layer information is missing). All of the examples above now (in version 3.0.2) raise the error

KeyNotFoundError: Your population does not have any layer keys, but your simulation does ['a']. If you called cv.People() directly, you probably need cv.make_people() instead.

But you're right that floats weren't being handled correctly before in this case; this has been fixed, so now e.g. this does work:

import covasim as cv
import sciris as sc

def make_save_people(pop_size=20e3, pop_infected=0, popfilename='mypop.pop'):

    ppl_pars = {'pop_size': pop_size, 
                'pop_infected': pop_infected,
                }

    # people = cv.People(pars=ppl_pars) # This won't work
    people = cv.make_people(cv.Sim(ppl_pars)) # But this does

    # Save object
    sc.saveobj(popfilename, people)
    return

popfilename='mypop-float.pop'
make_save_people(popfilename=popfilename)

sim_pars = dict(
    pop_size = 20e3,
)

sim = cv.Sim(pars=sim_pars,
             popfile=popfilename,
             load_pop=True)
sim.initialize() 
sim.run()

I had a look at making it so you could provide parameters directly to cv.make_people() instead of supplying a sim. It's possible, but makes the logic pretty convoluted. Since it's almost as easy to call cv.make_people(cv.Sim(ppl_pars)) as cv.make_people(ppl_pars) and since there would have to be a lot of duplication of parameter checking otherwise, I'm inclined to leave it as-is.

cliffckerr commented 3 years ago

Original bug fixed, and error message now more informative