To add a new indicator that uses Opus attributes, define an appropriate variable. (As usual, a good way to proceed is to find an existing definition that is similar to what you want, and copy and modify it.) If the indicator is a specialized one, it should be defined in a package specific to that application, rather than in the urbansim package. (And even if the indicator is of general utility, it should probably be in a separate package unless you are part of the core development team -- that way, if you upgrade to a version of the urbansim package, your indicator definition won't be lost.)
For example, here is the definition for the ``population'' variable in psrc.large_area.population.
class population(Variable):
"""Number of people in each area"""
_return_type="int32"
def dependencies(self):
return [attribute_label("faz", "large_area_id"),
attribute_label("faz", "population"),
my_attribute_label("large_area_id")]
def compute(self, dataset_pool):
faz = dataset_pool.get_dataset('faz')
return self.get_dataset().sum_over_ids(faz.get_attribute("large_area_id"),
faz.get_attribute("population"))
def post_check(self, values, dataset_pool):
size = dataset_pool.get_dataset('faz').get_attribute("population").sum()
self.do_check("x >= 0 and x <= " + str(size), values)
from opus_core.tests import opus_unittest
from urbansim.variable_test_toolbox import VariableTestToolbox
from numpy import array
from numpy import ma
class Tests(opus_unittest.OpusTestCase):
variable_name = "psrc.large_area.population"
def test_my_inputs(self):
population = array([21,22,27,42])
faz_large_area_ids = array([1,2,1,3])
faz_id = array([1,2,3,4])
values = VariableTestToolbox().compute_variable(self.variable_name, \
{"large_area":{
"large_area_id":array([1,2, 3])}, \
"faz":{ \
"population":population,\
"large_area_id":faz_large_area_ids, \
"faz_id":faz_id}}, \
dataset = "large_area")
should_be = array([48, 22, 42])
self.assertEqual(ma.allclose(values, should_be, rtol=1e-2), \
True, msg = "Error in " + self.variable_name)
if __name__=='__main__':
opus_unittest.main()
Again, this is just an ordinary Opus variable. Here
is a brief explanation of the different parts of the definition (see
also Section 7.4). We define a new class
population, which must be a subclass of Variable.
(The convention is that the names of Opus variables
are lower case, even though usually Python
class names are capitalized.) Its values depend on the
values of certain other variables, which are listed
in the dependencies method. The compute method is
where the action is: this defines how the values of this variable
are computed. In this case, we know that each
large_area contains a number of FAZ's (Forecast Analysis
Zones). The sum_over_ids method iterates through
the FAZ's, adding up populations depending on which
large_area the FAZ is in, and returns an array of
the population in each large_area. If invoked, the
post_check method performs a sanity check on the results:
is the population in each large_area between 0 and the total
population in all the grid cells? (See Section
10.3.) Finally, a unit test is defined
for this variable. This unit test, as with any other
Opus unit test, sets up just enough data to test whether the
variable's value is being computed correctly for a
small example (Section 10.2). The
if__name__=='__main__': part at the end means that the test
will be run if the file is run from the command line, but not if it
is merely imported.
Aside: Why not iterate over grid cells instead, you ask, since it's
ultimately the grid cells that keep track of households and hence
population? One reason is that in this implementation grid cells
know about the faz_id that contains the grid cell,
but not the large_area_id. So it wouldn't work. We could
add the large_area_id to each grid cell to make this work,
but that would use extra space (there are a lot of grid cells).
Further, if the populations of the FAZ's are needed for
any other purposes, these values will be computed on demand and
cached
-- and there are way fewer FAZ's than grid cells, making
the large_area computation more efficient.