201: Exampville Mode Choice

Welcome to Exampville, the best simulated town in this here part of the internet!

Exampville is a demonstration provided with Larch that walks through some of the data and tools that a transportation planner might use when building a travel model.

import os
import numpy as np
import pandas as pd 
import larch.numba as lx
from larch import P, X

In this example notebook, we will walk through the creation of a tour mode choice model.

To begin, we’ll re-load the tours and skims data from the data setup example.

hh, pp, tour, skims = lx.example(200, ['hh', 'pp', 'tour', 'skims'])

Preprocessing

The Exampville data output contains a set of files similar to what we might find for a real travel survey: network skims, and tables of households, persons, and tours. We’ll need to connect these tables together to create a composite dataset for mode choice model estimation, using the DataTree structure.

from addicty import Dict

Mode = Dict(
    DA = 1,
    SR = 2,
    Walk = 3,
    Bike = 4,
    Transit = 5,
).freeze()
tour_dataset = lx.Dataset.from_idco(tour.set_index('TOURID'), alts=Mode)
od_skims = lx.Dataset.from_omx(skims)

dt = lx.DataTree(
    tour=tour_dataset,
    hh=hh.set_index('HHID'),
    person=pp.set_index('PERSONID'),
    od=od_skims,
    do=od_skims,
    relationships=(
        "tours.HHID @ hh.HHID",
        "tours.PERSONID @ person.PERSONID",
        "hh.HOMETAZ @ od.otaz",
        "tours.DTAZ @ od.dtaz",
        "hh.HOMETAZ @ do.dtaz",
        "tours.DTAZ @ do.otaz",
    ),
)

In Exampville, there are only two kinds of trips:

  • work (purpose=1) and

  • non-work (purpose=2).

We want to estimate a mode choice model for work trips, so we’ll begin by excluding all the other trips:

dt_work = dt.query_cases("TOURPURP == 1")

Model Definition

And then we are ready to create our model.

m = lx.Model(datatree = dt_work)
m.title = "Exampville Work Tour Mode Choice v1"

We will explicitly define the set of utility functions we want to use. Because the DataFrames we are using to serve data to this model contains exclusively idco format data, we’ll use only the utility_co mapping to define a unique utility function for each alternative.

m.utility_co[Mode.DA] = (
        + P.InVehTime * X.AUTO_TIME
        + P.Cost * X.AUTO_COST # dollars per mile
)

m.utility_co[Mode.SR] = (
        + P.ASC_SR
        + P.InVehTime * X.AUTO_TIME
        + P.Cost * (X.AUTO_COST * 0.5) # dollars per mile, half share
        + P("LogIncome:SR") * X("log(INCOME)")
)

m.utility_co[Mode.Walk] = (
        + P.ASC_Walk
        + P.NonMotorTime * X.WALK_TIME
        + P("LogIncome:Walk") * X("log(INCOME)")
)

m.utility_co[Mode.Bike] = (
        + P.ASC_Bike
        + P.NonMotorTime * X.BIKE_TIME
        + P("LogIncome:Bike") * X("log(INCOME)")
)

m.utility_co[Mode.Transit] = (
        + P.ASC_Transit
        + P.InVehTime * X.TRANSIT_IVTT
        + P.OutVehTime * X.TRANSIT_OVTT
        + P.Cost * X.TRANSIT_FARE
        + P("LogIncome:Transit") * X('log(INCOME)')
)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [8], in <module>
----> 1 m.utility_co[Mode.DA] = (
      2         + P.InVehTime * X.AUTO_TIME
      3         + P.Cost * X.AUTO_COST # dollars per mile
      4 )
      6 m.utility_co[Mode.SR] = (
      7         + P.ASC_SR
      8         + P.InVehTime * X.AUTO_TIME
      9         + P.Cost * (X.AUTO_COST * 0.5) # dollars per mile, half share
     10         + P("LogIncome:SR") * X("log(INCOME)")
     11 )
     13 m.utility_co[Mode.Walk] = (
     14         + P.ASC_Walk
     15         + P.NonMotorTime * X.WALK_TIME
     16         + P("LogIncome:Walk") * X("log(INCOME)")
     17 )

AttributeError: 'NoneType' object has no attribute 'DA'

To write a nested logit mode, we’ll attach some nesting nodes to the model’s graph. Each new_node allows us to define the set of codes for the child nodes (elemental alternatives, or lower level nests) as well as giving the new nest a name and assigning a logsum parameter. The return value of this method is the node code for the newly created nest, which then can potenially be used as a child code when creating a higher level nest. We do this below, adding the ‘Car’ nest into the ‘Motor’ nest.

Car = m.graph.new_node(parameter='Mu:Car', children=[Mode.DA, Mode.SR], name='Car')
NonMotor = m.graph.new_node(parameter='Mu:NonMotor', children=[Mode.Walk, Mode.Bike], name='NonMotor')
Motor = m.graph.new_node(parameter='Mu:Motor', children=[Car, Mode.Transit], name='Motor')
/home/runner/work/larch/larch/larch/larch/dataset.py:1148: UserWarning: no defined ALTID
  warnings.warn("no defined ALTID")
/home/runner/work/larch/larch/larch/larch/numba/model.py:899: UserWarning: cannot initialize graph, must define alternatives somehow
  warnings.warn('cannot initialize graph, must define alternatives somehow')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [9], in <module>
----> 1 Car = m.graph.new_node(parameter='Mu:Car', children=[Mode.DA, Mode.SR], name='Car')
      2 NonMotor = m.graph.new_node(parameter='Mu:NonMotor', children=[Mode.Walk, Mode.Bike], name='NonMotor')
      3 Motor = m.graph.new_node(parameter='Mu:Motor', children=[Car, Mode.Transit], name='Motor')

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow

Let’s visually check on the nesting structure.

m.graph
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [10], in <module>
----> 1 m.graph

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow

The tour mode choice model’s choice variable is indicated by the code value in ‘TOURMODE’, and this can be defined for the model using choice_co_code.

m.choice_co_code = 'TOURMODE'

We can also give a dictionary of availability conditions based on values in the idco data, using the availability_co_vars attribute. Alternatives that are always available can be indicated by setting the criterion to 1.

m.availability_co_vars = {
    Mode.DA: 'AGE >= 16',
    Mode.SR: 1,
    Mode.Walk: 'WALK_TIME < 60',
    Mode.Bike: 'BIKE_TIME < 60',
    Mode.Transit: 'TRANSIT_FARE>0',
}
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [12], in <module>
      1 m.availability_co_vars = {
----> 2     Mode.DA: 'AGE >= 16',
      3     Mode.SR: 1,
      4     Mode.Walk: 'WALK_TIME < 60',
      5     Mode.Bike: 'BIKE_TIME < 60',
      6     Mode.Transit: 'TRANSIT_FARE>0',
      7 }

AttributeError: 'NoneType' object has no attribute 'DA'

Then let’s prepare this data for estimation. Even though the data is already in memory, the load_data method is used to pre-process the data, extracting the required values, pre-computing the values of fixed expressions, and assembling the results into contiguous arrays suitable for computing the log likelihood values efficiently.

Model Estimation

We can check on some important statistics of this loaded data even before we estimate the model.

m.choice_avail_summary()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [13], in <module>
----> 1 m.choice_avail_summary()

File ~/work/larch/larch/larch/larch/numba/model.py:2059, in NumbaModel.choice_avail_summary(self)
   2051 """
   2052 Generate a summary of choice and availability statistics.
   2053 
   (...)
   2056 pandas.DataFrame
   2057 """
   2058 from ..dataset import choice_avail_summary
-> 2059 self.unmangle()
   2060 graph = None if self.is_mnl() else self.graph
   2061 return choice_avail_summary(
   2062     self.dataset,
   2063     graph,
   2064     self.availability_co_vars,
   2065 )

File ~/work/larch/larch/larch/larch/numba/model.py:1118, in NumbaModel.unmangle(self, force)
   1116 super().unmangle(force=force)
   1117 if self._dataset is None or force:
-> 1118     self.reflow_data_arrays()
   1119 if self._fixed_arrays is None or force:
   1120     self._rebuild_fixed_arrays()

File ~/work/larch/larch/larch/larch/numba/model.py:977, in NumbaModel.reflow_data_arrays(self)
    973 def reflow_data_arrays(self):
    974     """
    975     Reload the internal data_arrays so they are consistent with the datatree.
    976     """
--> 977     if self.graph is None:
    978         self._data_arrays = None
    979         return

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow

If we are satisfied with the statistics we see above, we can go ahead and estimate the model.

m.set_cap(20) # improves optimization stability
result = m.maximize_loglike()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [15], in <module>
----> 1 m.set_cap(20) # improves optimization stability
      2 result = m.maximize_loglike()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:622, in larch.model.parameter_frame.ParameterFrame.set_cap()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:195, in larch.model.parameter_frame.ParameterFrame.pf.__get__()

File ~/work/larch/larch/larch/larch/numba/model.py:1118, in NumbaModel.unmangle(self, force)
   1116 super().unmangle(force=force)
   1117 if self._dataset is None or force:
-> 1118     self.reflow_data_arrays()
   1119 if self._fixed_arrays is None or force:
   1120     self._rebuild_fixed_arrays()

File ~/work/larch/larch/larch/larch/numba/model.py:977, in NumbaModel.reflow_data_arrays(self)
    973 def reflow_data_arrays(self):
    974     """
    975     Reload the internal data_arrays so they are consistent with the datatree.
    976     """
--> 977     if self.graph is None:
    978         self._data_arrays = None
    979         return

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow

After we find the best fitting parameters, we can compute some variance-covariance statistics, incuding standard errors of the estimates and t statistics, using calculate_parameter_covariance.

m.calculate_parameter_covariance()
error in calculate_parameter_covariance
Traceback (most recent call last):
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 896, in graph
    self.initialize_graph()
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 966, in initialize_graph
    super().initialize_graph(
  File "larch/model/controller.pyx", line 1308, in larch.model.controller.Model5c.initialize_graph
ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "larch/model/abstract_model.pyx", line 1065, in larch.model.abstract_model.AbstractChoiceModel.calculate_parameter_covariance
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 1773, in d2_loglike
    return super().d2_loglike(
  File "larch/model/abstract_model.pyx", line 566, in larch.model.abstract_model.AbstractChoiceModel.d2_loglike
  File "larch/model/abstract_model.pyx", line 417, in larch.model.abstract_model.AbstractChoiceModel.loglike3
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 1706, in loglike2
    result_arrays, penalty = self._loglike_runner(
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 1339, in _loglike_runner
    args = self.__prepare_for_compute(
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 1172, in __prepare_for_compute
    self.unmangle()
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 1118, in unmangle
    self.reflow_data_arrays()
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 977, in reflow_data_arrays
    if self.graph is None:
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 900, in graph
    raise RuntimeError('cannot initialize graph, must define alternatives somehow')
RuntimeError: cannot initialize graph, must define alternatives somehow
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [17], in <module>
----> 1 m.calculate_parameter_covariance()

File ~/work/larch/larch/larch/larch/model/abstract_model.pyx:1230, in larch.model.abstract_model.AbstractChoiceModel.calculate_parameter_covariance()

File ~/work/larch/larch/larch/larch/model/abstract_model.pyx:1065, in larch.model.abstract_model.AbstractChoiceModel.calculate_parameter_covariance()

File ~/work/larch/larch/larch/larch/numba/model.py:1773, in NumbaModel.d2_loglike(self, x, start_case, stop_case, step_case, leave_out, keep_only, subsample)
   1762 def d2_loglike(
   1763         self,
   1764         x=None,
   (...)
   1771         subsample=-1,
   1772 ):
-> 1773     return super().d2_loglike(
   1774         x=x,
   1775         start_case=start_case,
   1776         stop_case=stop_case,
   1777         step_case=step_case,
   1778         leave_out=leave_out,
   1779         keep_only=keep_only,
   1780         subsample=subsample,
   1781     )

File ~/work/larch/larch/larch/larch/model/abstract_model.pyx:566, in larch.model.abstract_model.AbstractChoiceModel.d2_loglike()

File ~/work/larch/larch/larch/larch/model/abstract_model.pyx:417, in larch.model.abstract_model.AbstractChoiceModel.loglike3()

File ~/work/larch/larch/larch/larch/numba/model.py:1706, in NumbaModel.loglike2(self, x, start_case, stop_case, step_case, persist, leave_out, keep_only, subsample, return_series, probability_only)
   1692 def loglike2(
   1693         self,
   1694         x=None,
   (...)
   1704         probability_only=False,
   1705 ):
-> 1706     result_arrays, penalty = self._loglike_runner(
   1707         x,
   1708         start_case=start_case,
   1709         stop_case=stop_case,
   1710         step_case=step_case,
   1711         return_gradient=True,
   1712     )
   1713     result = dictx(
   1714         ll=result_arrays.loglike.sum() * self.weight_normalization,
   1715         dll=result_arrays.d_loglike.sum(0) * self.weight_normalization,
   1716     )
   1717     if start_case is None and stop_case is None and step_case is None:

File ~/work/larch/larch/larch/larch/numba/model.py:1339, in NumbaModel._loglike_runner(self, x, only_utility, return_gradient, return_probability, return_bhhh, start_case, stop_case, step_case)
   1327 def _loglike_runner(
   1328         self,
   1329         x=None,
   (...)
   1336         step_case=None,
   1337 ):
   1338     caseslice = slice(start_case, stop_case, step_case)
-> 1339     args = self.__prepare_for_compute(
   1340         x,
   1341         allow_missing_ch=return_probability or (only_utility>0),
   1342         caseslice=caseslice,
   1343     )
   1344     args_flags = args + (np.asarray([
   1345         only_utility,
   1346         return_probability,
   1347         return_gradient,
   1348         return_bhhh,
   1349     ], dtype=np.int8),)
   1350     try:

File ~/work/larch/larch/larch/larch/numba/model.py:1172, in NumbaModel.__prepare_for_compute(self, x, allow_missing_ch, allow_missing_av, caseslice)
   1170 if self._dataframes is not None and not self._dataframes.is_computational_ready(activate=True):
   1171     raise ValueError('DataFrames is not computational-ready')
-> 1172 self.unmangle()
   1173 if x is not None:
   1174     self.set_values(x)

File ~/work/larch/larch/larch/larch/numba/model.py:1118, in NumbaModel.unmangle(self, force)
   1116 super().unmangle(force=force)
   1117 if self._dataset is None or force:
-> 1118     self.reflow_data_arrays()
   1119 if self._fixed_arrays is None or force:
   1120     self._rebuild_fixed_arrays()

File ~/work/larch/larch/larch/larch/numba/model.py:977, in NumbaModel.reflow_data_arrays(self)
    973 def reflow_data_arrays(self):
    974     """
    975     Reload the internal data_arrays so they are consistent with the datatree.
    976     """
--> 977     if self.graph is None:
    978         self._data_arrays = None
    979         return

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow

Then we can review the results in a variety of report tables.

m.parameter_summary()
error in parameter_summary
Traceback (most recent call last):
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 896, in graph
    self.initialize_graph()
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 966, in initialize_graph
    super().initialize_graph(
  File "larch/model/controller.pyx", line 1308, in larch.model.controller.Model5c.initialize_graph
ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "larch/model/parameter_frame.pyx", line 848, in larch.model.parameter_frame.ParameterFrame.parameter_summary
  File "larch/model/parameter_frame.pyx", line 661, in larch.model.parameter_frame.ParameterFrame.pfo
  File "larch/model/parameter_frame.pyx", line 195, in larch.model.parameter_frame.ParameterFrame.pf.__get__
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 1118, in unmangle
    self.reflow_data_arrays()
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 977, in reflow_data_arrays
    if self.graph is None:
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 900, in graph
    raise RuntimeError('cannot initialize graph, must define alternatives somehow')
RuntimeError: cannot initialize graph, must define alternatives somehow
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [18], in <module>
----> 1 m.parameter_summary()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:1008, in larch.model.parameter_frame.ParameterFrame.parameter_summary()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:848, in larch.model.parameter_frame.ParameterFrame.parameter_summary()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:661, in larch.model.parameter_frame.ParameterFrame.pfo()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:195, in larch.model.parameter_frame.ParameterFrame.pf.__get__()

File ~/work/larch/larch/larch/larch/numba/model.py:1118, in NumbaModel.unmangle(self, force)
   1116 super().unmangle(force=force)
   1117 if self._dataset is None or force:
-> 1118     self.reflow_data_arrays()
   1119 if self._fixed_arrays is None or force:
   1120     self._rebuild_fixed_arrays()

File ~/work/larch/larch/larch/larch/numba/model.py:977, in NumbaModel.reflow_data_arrays(self)
    973 def reflow_data_arrays(self):
    974     """
    975     Reload the internal data_arrays so they are consistent with the datatree.
    976     """
--> 977     if self.graph is None:
    978         self._data_arrays = None
    979         return

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow
m.estimation_statistics()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [20], in <module>
----> 1 m.estimation_statistics()

File ~/work/larch/larch/larch/larch/model/abstract_model.pyx:922, in larch.model.abstract_model.AbstractChoiceModel.estimation_statistics()

File ~/work/larch/larch/larch/larch/model/abstract_model.pyx:736, in larch.model.abstract_model.AbstractChoiceModel.loglike_null()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:631, in larch.model.parameter_frame.ParameterFrame.get_values()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:195, in larch.model.parameter_frame.ParameterFrame.pf.__get__()

File ~/work/larch/larch/larch/larch/numba/model.py:1118, in NumbaModel.unmangle(self, force)
   1116 super().unmangle(force=force)
   1117 if self._dataset is None or force:
-> 1118     self.reflow_data_arrays()
   1119 if self._fixed_arrays is None or force:
   1120     self._rebuild_fixed_arrays()

File ~/work/larch/larch/larch/larch/numba/model.py:977, in NumbaModel.reflow_data_arrays(self)
    973 def reflow_data_arrays(self):
    974     """
    975     Reload the internal data_arrays so they are consistent with the datatree.
    976     """
--> 977     if self.graph is None:
    978         self._data_arrays = None
    979         return

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow

Save and Report Model

report = lx.Reporter(title=m.title)
report.append('# Parameter Summary')
report.append(m.parameter_summary())
report
error in parameter_summary
Traceback (most recent call last):
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 896, in graph
    self.initialize_graph()
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 966, in initialize_graph
    super().initialize_graph(
  File "larch/model/controller.pyx", line 1308, in larch.model.controller.Model5c.initialize_graph
ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "larch/model/parameter_frame.pyx", line 848, in larch.model.parameter_frame.ParameterFrame.parameter_summary
  File "larch/model/parameter_frame.pyx", line 661, in larch.model.parameter_frame.ParameterFrame.pfo
  File "larch/model/parameter_frame.pyx", line 195, in larch.model.parameter_frame.ParameterFrame.pf.__get__
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 1118, in unmangle
    self.reflow_data_arrays()
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 977, in reflow_data_arrays
    if self.graph is None:
  File "/home/runner/work/larch/larch/larch/larch/numba/model.py", line 900, in graph
    raise RuntimeError('cannot initialize graph, must define alternatives somehow')
RuntimeError: cannot initialize graph, must define alternatives somehow
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [22], in <module>
      1 report.append('# Parameter Summary')
----> 2 report.append(m.parameter_summary())
      3 report

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:1008, in larch.model.parameter_frame.ParameterFrame.parameter_summary()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:848, in larch.model.parameter_frame.ParameterFrame.parameter_summary()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:661, in larch.model.parameter_frame.ParameterFrame.pfo()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:195, in larch.model.parameter_frame.ParameterFrame.pf.__get__()

File ~/work/larch/larch/larch/larch/numba/model.py:1118, in NumbaModel.unmangle(self, force)
   1116 super().unmangle(force=force)
   1117 if self._dataset is None or force:
-> 1118     self.reflow_data_arrays()
   1119 if self._fixed_arrays is None or force:
   1120     self._rebuild_fixed_arrays()

File ~/work/larch/larch/larch/larch/numba/model.py:977, in NumbaModel.reflow_data_arrays(self)
    973 def reflow_data_arrays(self):
    974     """
    975     Reload the internal data_arrays so they are consistent with the datatree.
    976     """
--> 977     if self.graph is None:
    978         self._data_arrays = None
    979         return

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow
report << "# Estimation Statistics" << m.estimation_statistics()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [23], in <module>
----> 1 report << "# Estimation Statistics" << m.estimation_statistics()

File ~/work/larch/larch/larch/larch/model/abstract_model.pyx:922, in larch.model.abstract_model.AbstractChoiceModel.estimation_statistics()

File ~/work/larch/larch/larch/larch/model/abstract_model.pyx:736, in larch.model.abstract_model.AbstractChoiceModel.loglike_null()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:631, in larch.model.parameter_frame.ParameterFrame.get_values()

File ~/work/larch/larch/larch/larch/model/parameter_frame.pyx:195, in larch.model.parameter_frame.ParameterFrame.pf.__get__()

File ~/work/larch/larch/larch/larch/numba/model.py:1118, in NumbaModel.unmangle(self, force)
   1116 super().unmangle(force=force)
   1117 if self._dataset is None or force:
-> 1118     self.reflow_data_arrays()
   1119 if self._fixed_arrays is None or force:
   1120     self._rebuild_fixed_arrays()

File ~/work/larch/larch/larch/larch/numba/model.py:977, in NumbaModel.reflow_data_arrays(self)
    973 def reflow_data_arrays(self):
    974     """
    975     Reload the internal data_arrays so they are consistent with the datatree.
    976     """
--> 977     if self.graph is None:
    978         self._data_arrays = None
    979         return

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow
report << "# Utility Functions" << m.utility_functions()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/larch/larch/larch/larch/numba/model.py:896, in NumbaModel.graph(self)
    895 try:
--> 896     self.initialize_graph()
    897 except ValueError:

File ~/work/larch/larch/larch/larch/numba/model.py:966, in NumbaModel.initialize_graph(self, dataframes, alternative_codes, alternative_names, root_id)
    961         alternative_names = get_coords_array(
    962             'altname', 'altnames', 'alt_name', 'alt_names',
    963             'alternative_name', 'alternative_names',
    964         )
--> 966 super().initialize_graph(
    967     dataframes=dataframes,
    968     alternative_codes=alternative_codes,
    969     alternative_names=alternative_names,
    970     root_id=root_id,
    971 )

File ~/work/larch/larch/larch/larch/model/controller.pyx:1308, in larch.model.controller.Model5c.initialize_graph()

ValueError: must define dataframes, dataservice, or give alternative_codes explicitly

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Input In [24], in <module>
----> 1 report << "# Utility Functions" << m.utility_functions()

File ~/work/larch/larch/larch/larch/model/model.py:470, in Model.utility_functions(self, subset, resolve_parameters)
    450 def utility_functions(self, subset=None, resolve_parameters=False):
    451 	"""
    452 	Generate an XHTML output of the utility function(s).
    453 
   (...)
    468 	xmle.Elem
    469 	"""
--> 470 	self.unmangle()
    471 	from xmle import Elem
    472 	x = Elem('div')

File ~/work/larch/larch/larch/larch/numba/model.py:1118, in NumbaModel.unmangle(self, force)
   1116 super().unmangle(force=force)
   1117 if self._dataset is None or force:
-> 1118     self.reflow_data_arrays()
   1119 if self._fixed_arrays is None or force:
   1120     self._rebuild_fixed_arrays()

File ~/work/larch/larch/larch/larch/numba/model.py:977, in NumbaModel.reflow_data_arrays(self)
    973 def reflow_data_arrays(self):
    974     """
    975     Reload the internal data_arrays so they are consistent with the datatree.
    976     """
--> 977     if self.graph is None:
    978         self._data_arrays = None
    979         return

File ~/work/larch/larch/larch/larch/numba/model.py:900, in NumbaModel.graph(self)
    898         import warnings
    899         warnings.warn('cannot initialize graph, must define alternatives somehow')
--> 900         raise RuntimeError('cannot initialize graph, must define alternatives somehow')
    901 return self._graph

RuntimeError: cannot initialize graph, must define alternatives somehow
report.save(
    '/tmp/exampville_mode_choice.html', 
    overwrite=True, 
    metadata=m,
)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [25], in <module>
----> 1 report.save(
      2     '/tmp/exampville_mode_choice.html', 
      3     overwrite=True, 
      4     metadata=m,
      5 )

File /usr/share/miniconda3/envs/development/lib/python3.9/site-packages/xmle/reporter.py:97, in Reporter.save(self, filename, overwrite, archive_dir, metadata)
     94 self.renumber_numbered_items()
     96 from .xhtml import XHTML
---> 97 with XHTML(
     98 		filename,
     99 		overwrite=overwrite,
    100 		metadata=metadata,
    101 		archive_dir=archive_dir,
    102 		title=self._short_title,
    103 ) as f:
    104 	f << self
    105 return f._filename

File /usr/share/miniconda3/envs/development/lib/python3.9/site-packages/xmle/xhtml.py:335, in XHTML.__init__(self, filename, overwrite, archive_dir, title, css, extra_css, jquery, jqueryui, floating_tablehead, metadata, toc, favicon)
    333 except ImportError:
    334 	import pickle as cloudpickle
--> 335 _meta = (base64.standard_b64encode(zlib.compress(cloudpickle.dumps(metadata)))).decode()
    336 self.head << Elem(tag="meta", name='pythonmetadata', content=_meta)

File /usr/share/miniconda3/envs/development/lib/python3.9/site-packages/cloudpickle/cloudpickle_fast.py:73, in dumps(obj, protocol, buffer_callback)
     69 with io.BytesIO() as file:
     70     cp = CloudPickler(
     71         file, protocol=protocol, buffer_callback=buffer_callback
     72     )
---> 73     cp.dump(obj)
     74     return file.getvalue()

File /usr/share/miniconda3/envs/development/lib/python3.9/site-packages/cloudpickle/cloudpickle_fast.py:602, in CloudPickler.dump(self, obj)
    600 def dump(self, obj):
    601     try:
--> 602         return Pickler.dump(self, obj)
    603     except RuntimeError as e:
    604         if "recursion" in e.args[0]:

File ~/work/larch/larch/larch/larch/numba/model.py:1927, in NumbaModel.__getstate__(self)
   1921 def __getstate__(self):
   1922     state = dict(
   1923         float_dtype=self.float_dtype,
   1924         constraint_intensity=self.constraint_intensity,
   1925         constraint_sharpness=self.constraint_sharpness,
   1926         _constraint_funcs=self._constraint_funcs,
-> 1927         _private__graph=self._private__graph,
   1928     )
   1929     return super().__getstate__(), state

AttributeError: 'NumbaModel' object has no attribute '_private__graph'