next up previous index
Next: Opus and UrbanSim Reference Up: Generating and Visualizing Indicators Previous: When to Compute Indicator   Index


Defining New Indicators

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.


next up previous index
Next: Opus and UrbanSim Reference Up: Generating and Visualizing Indicators Previous: When to Compute Indicator   Index
info (at) urbansim.org