Skip to content

Estimation Enhancements #917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 55 commits into
base: main
Choose a base branch
from

Conversation

dhensle
Copy link
Contributor

@dhensle dhensle commented Dec 17, 2024

Estimation work as part of ActivitySim's Phase 9B development effort.

  • Implement multiprocessing for estimation mode
  • Implement destination choice sampling in estimation mode
  • Change the formatting and data written to EDBs
  • Update Larch integration to accept new file formats
  • Functionality to quickly test different specifications
  • Improved larch reporting on estimated models
  • Adding "predict" functionality with estimated models in larch
  • Unit testing for the above features
  • Updated documentation

dhensle and others added 30 commits August 16, 2024 17:33
* pydantic for estimation settings

* allow df as type in config

* fix table_info

* repair for Pydantic

* df is attribute
* pydantic for estimation settings

* allow df as type in config

* fix table_info

* auto ownership

* repair for pydantic

* update for ruff

* updated for simple models

* repair for Pydantic

* simple simulate and location choice

* df is attribute

* scheduling

* stop freq

* test locations

* cdap

* nonmand_and_joint_tour_dest_choice

* nonmand_tour_freq

* fix ci to stop using mamba

* test updates

* use larch6 from pip

* use numba for stop freq

* fix for pandas 1.5

* fix stop freq test for numba

* Sharrow Cache Dir Setting (ActivitySim#893)

* setting necessary filesystem changes from settings file

* set for multiprocessing

* repair github actions

* github action updates (ActivitySim#903)

* script to make data

* unified script for making data

* remove older

* bug

* doc note

* load from parquet if available

* add original alt ids to EDB output when using compact

* fix MP race

* script arg to skip to EDB

* clean up CDAP and blacken

* refactor model_estimation_table_types

change to estimation_table_types, to avoid pydantic namespace clash

* repair drop_dupes

* blacken

* location choice with compact

* choice_def for compact

* spec changes for simple-simulate

* re-estimation demo for auto ownership

* clean up status messages

* change name to stop pydantic warnings

* edit configs

* default estimation sample size is same as regular sample size

* allow location alts not in cv format

* dummy zones for location choice

* update scheduling model estimation

* various cleanup

* stop freq

* tidy build script

* update 02 school location for larger example

* update notebook 04

* editable model re-estimation for location choice

* fix test names

* update notebooks

* cdap print filenames as loading

* notebook 07

* tests thru 07

* notebooks 08 09

* build the data first

* runnable script

* change larch version dependency

* keep pandas<2

* notebooks 10 11

* notebook 12

* remove odd print

* add matplotlib

* notebook 13 14

* test all the notebooks

* add xlsxwriter to tests

* notebook 15

* CDAP revise model spec demo

* notebook 16

* notebook 17

* longer timeout

* notebook 18

* notebook 19

* notebook 20

* smaller notebook 15

* configurable est mode setup

* notebook 21

* notebook 22

* config sample size in GA

* notebook 23

* updates for larch and graphviz

* change default to compact

* compare model 03

* test updates

* rename test targets

* repair_av_zq

* move doctor up

* add another repair

* oops

---------

Co-authored-by: David Hensle <[email protected]>
@dhensle dhensle marked this pull request as ready for review May 15, 2025 18:31
@jpn-- jpn-- requested a review from i-am-sijia May 15, 2025 22:42
@asiripanich
Copy link
Contributor

asiripanich commented May 21, 2025

Hi @asiripanich, thank you for your interest and testing! We are just putting the finishing touches on this and hope to have it pulled in by the end of the month.

Any feedback from you on the new features? Now's the time to get any changes you might want put in!

I have been able to get this enhanced estimation mode to work with our travel survey. The parquet EBDs are definitely a huge improvement over the CSVs. Thanks for the work!

A few comments:

  • I remember the CDAP estimation function wasn't working because my ActivitySim outputs were in Parquet format. I had to add a line to reset the index when reading in Parquet inputs; this is not an issue if you are reading in CSV files.
  • I feel like the specification files could be more compact if the coefficient values weren't separated into another file just to specify their constraint values. I understand this would require some work and planning, but how about adding a symbol (e.g., an asterisk *) in front of the coefficient values in the specification file if you want to fix their value? Creating a new label column and coefficient names feels unnecessary and just increases the number of input files that one has to manage, which means a higher chance of making errors. If the label field is required in the specification file, I think it could be automatically generated from the description field by converting the description to underscore case.

Example:

Description Expression M N H
Full-time worker alternative-specific constants ptype == 1 *0.885080091 0.531583624
Part-time worker alternative-specific constants ptype == 2 *-0.920808727 1.117988879
University student alternative-specific constants ptype == 3 1.898468936 -0.380144113

@jpn-- jpn-- mentioned this pull request May 31, 2025
@AmirSamimi-TfNSW
Copy link

AmirSamimi-TfNSW commented Jun 5, 2025

Hi @dhensle

I tested the new estimation tool. Great work! I did notice a minor bug related to the file naming. When generating the EDB for school_location, for example, the tool creates a CSV file named school_location_school_location_coeffs.csv instead of school_location_coefficients.csv

For now, I’m manually renaming the file, but it might be a small bug worth checking in the naming logic.
Thanks!

@i-am-sijia i-am-sijia requested review from johnpgliebe and andkay and removed request for i-am-sijia June 6, 2025 14:17
@dhensle
Copy link
Contributor Author

dhensle commented Jun 11, 2025

Hi @dhensle

I tested the new estimation tool. Great work! I did notice a minor bug related to the file naming. When generating the EDB for school_location, for example, the tool creates a CSV file named school_location_school_location_coeffs.csv instead of school_location_coefficients.csv

For now, I’m manually renaming the file, but it might be a small bug worth checking in the naming logic. Thanks!

Hi @AmirSamimi-TfNSW, thanks for your testing!

The code should be writing the coefficients file with the file name specified in the model yaml settings file. Can you please check the COEFFICIENT setting in your school_location.yaml and see if this naming issue stems from that file? I do not see this issue in any of my outputs.

@dhensle
Copy link
Contributor Author

dhensle commented Jun 11, 2025

Hi @asiripanich, thank you for your interest and testing! We are just putting the finishing touches on this and hope to have it pulled in by the end of the month.
Any feedback from you on the new features? Now's the time to get any changes you might want put in!

I have been able to get this enhanced estimation mode to work with our travel survey. The parquet EBDs are definitely a huge improvement over the CSVs. Thanks for the work!

A few comments:

  • I remember the CDAP estimation function wasn't working because my ActivitySim outputs were in Parquet format. I had to add a line to reset the index when reading in Parquet inputs; this is not an issue if you are reading in CSV files.
  • I feel like the specification files could be more compact if the coefficient values weren't separated into another file just to specify their constraint values. I understand this would require some work and planning, but how about adding a symbol (e.g., an asterisk *) in front of the coefficient values in the specification file if you want to fix their value? Creating a new label column and coefficient names feels unnecessary and just increases the number of input files that one has to manage, which means a higher chance of making errors. If the label field is required in the specification file, I think it could be automatically generated from the description field by converting the description to underscore case.

Example:

Description Expression M N H
Full-time worker alternative-specific constants ptype == 1 *0.885080091 0.531583624
Part-time worker alternative-specific constants ptype == 2 *-0.920808727 1.117988879
University student alternative-specific constants ptype == 3 1.898468936 -0.380144113

Hi @asiripanich glad the new EDBs have been working for you! In response to your comments:

  • CDAP is unique in that it also will read the person and household files. I have updated the parquet read code that exists in this model to handle that indexing issue. Thanks for flagging.
  • The decision to move coefficients into a separate file was made when estimation mode was first implemented. I agree that the coefficient values are much harder to see with them being stored in a different file. However, having coefficient names that can be used and estimated across multiple utility terms is the primary reason this change was done. Take for example the cost and in-vehicle time coefficients in mode choice --- they are used numerous times across modes and utility terms. Specifying the coefficient values separately from the spec makes this much easier to see and much easier to estimate. If you would like to have a broader discussion around how we handle coefficient values in ActivitySim configs, I would welcome that discussion in a separate issue as it is outside the scope of this PR. (Perhaps we just need to create a little utility that will output a spec with coefficient values applied for human-readability?)

Thanks for your comments!

@asiripanich
Copy link
Contributor

asiripanich commented Jun 24, 2025

Thanks @dhensle for your response.

I ran into an issue today while trying to add a new utility term to the tour mode choice spec of my tour mode choice EDB. This might be a bug or something that needs to be explained a bit more in the documentation. (However, I didn't have any issues modifying or adding new terms to the auto_ownership model. Despite this minor issue, I'm loving this new capability!)

When I tried to add a new line to my tour mode choice spec file, I kept getting this error:

modelname = "tour_mode_choice"

from activitysim.estimation.larch import component_model

model, data = component_model(
    modelname,
    edb_directory=f"output/estimation_data_bundle/{modelname}/",
    return_data=True,
)

> loading from output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_coefficients.csv
> loading from output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_coefficients_template.csv
> loading spec from output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_SPEC.csv
> loading from output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_values_combined.parquet
> unable to rewrite 'util_test' to itself

I tested this using example_estimation and 17_tour_mode_choice.ipynb from this PR.

I added these following lines:

tour_mode_choice_SPEC.csv

util_test,Drive alone not available for escort tours,1,coef_test,,,,,,,,,,,,,,,,,,,,

tour_mode_choice_coefficients_template.csv

coef_test,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_school_univ,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_school_univ,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork

tour_mode_choice_coefficients.csv

coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,0,F
coef_test_school_univ,0,F

Any advice on what I might be doing wrong would be greatly appreciated. 😀

See full error

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:942, in DataTree.get_expr(self, expression, engine, allow_native, dtype, with_coords, parser)
    941     else:
--> [942](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:942)         raise KeyError
    943 except (KeyError, IndexError):

KeyError: 

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:958, in DataTree.get_expr(self, expression, engine, allow_native, dtype, with_coords, parser)
    956 try:
    957     result = DataArray(
--> [958](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:958)         ne.evaluate(expression, local_dict=CachedTree(self)),
    959     )
    960 except Exception:

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/numexpr/necompiler.py:975, in evaluate(ex, local_dict, global_dict, out, order, casting, sanitize, _frame_depth, **kwargs)
    974 else:
--> [975](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/numexpr/necompiler.py:975)     raise e

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/numexpr/necompiler.py:874, in validate(ex, local_dict, global_dict, out, order, casting, _frame_depth, sanitize, **kwargs)
    873 names, ex_uses_vml = _names_cache[expr_key]
--> [874](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/numexpr/necompiler.py:874) arguments = getArguments(names, local_dict, global_dict, _frame_depth=_frame_depth)
    876 # Create a signature

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/numexpr/necompiler.py:764, in getArguments(names, local_dict, global_dict, _frame_depth)
    763 except KeyError:
--> [764](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/numexpr/necompiler.py:764)     a = global_dict[name]
    765 arguments.append(numpy.asarray(a))

KeyError: 'util_test'

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/dataset/__init__.py:422, in DataTree.setup_flow(self, *args, **kwargs)
    421 try:
--> [422](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/dataset/__init__.py:422)     return super().setup_flow(*args, **kwargs)
    423 except ValueError as err:

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1535, in DataTree.setup_flow(self, definition_spec, cache_dir, name, dtype, boundscheck, error_model, nopython, fastmath, parallel, readme, flow_library, extra_hash_data, write_hash_audit, hashing_level, dim_exclude, with_root_node_name)
   1533 from .flows import Flow
-> [1535](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1535) return Flow(
   1536     self,
   1537     definition_spec,
   1538     cache_dir=cache_dir or self.cache_dir,
   1539     name=name,
   1540     dtype=dtype,
   1541     boundscheck=boundscheck,
   1542     nopython=nopython,
   1543     fastmath=fastmath,
   1544     parallel=parallel,
   1545     readme=readme,
   1546     flow_library=flow_library,
   1547     extra_hash_data=extra_hash_data,
   1548     hashing_level=hashing_level,
   1549     error_model=error_model,
   1550     write_hash_audit=write_hash_audit,
   1551     dim_order=self.dim_order,
   1552     dim_exclude=dim_exclude,
   1553     with_root_node_name=with_root_node_name,
   1554 )

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1053, in Flow.__new__(cls, tree, defs, error_model, cache_dir, name, dtype, boundscheck, nopython, fastmath, parallel, readme, flow_library, extra_hash_data, write_hash_audit, hashing_level, dim_order, dim_exclude, bool_wrapping, with_root_node_name, parallel_irunner, parallel_idotter)
   1052 # otherwise finish normal init
-> [1053](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1053) self.__initialize_2(
   1054     defs,
   1055     error_model=error_model,
   1056     name=name,
   1057     dtype=dtype,
   1058     boundscheck=boundscheck,
   1059     nopython=nopython,
   1060     fastmath=fastmath,
   1061     readme=readme,
   1062     parallel=parallel,
   1063     extra_hash_data=extra_hash_data,
   1064     write_hash_audit=write_hash_audit,
   1065     with_root_node_name=with_root_node_name,
   1066     parallel_idotter=parallel_idotter,
   1067     parallel_irunner=parallel_irunner,
   1068 )
   1069 if flow_library is not None:

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1620, in Flow.__initialize_2(self, defs, error_model, name, dtype, boundscheck, nopython, fastmath, readme, parallel, extra_hash_data, write_hash_audit, with_root_node_name, parallel_irunner, parallel_idotter)
   1619 if self._hashing_level <= 1:
-> [1620](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1620)     func_code, all_name_tokens = self.init_sub_funcs(
   1621         defs,
   1622         error_model=error_model,
   1623         boundscheck=boundscheck,
   1624         nopython=nopython,
   1625         fastmath=fastmath,
   1626     )
   1627     self._func_code = func_code

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1546, in Flow.init_sub_funcs(self, defs, error_model, boundscheck, nopython, fastmath)
   1545     logger.error(f"unable to rewrite '{k}' to itself")
-> [1546](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1546)     raise ValueError(f"unable to rewrite '{k}' to itself")
   1547 logger.debug(f"[{k}] rewrite {init_expr} -> {expr}")

ValueError: unable to rewrite 'util_test' to itself

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1073, in DataTree.eval(self, expression, engine, dtype, name, with_coords)
   1072 try:
-> [1073](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1073)     result = self.get_expr(
   1074         expression,
   1075         "numexpr",
   1076         allow_native=False,
   1077         dtype=dtype,
   1078         with_coords=with_coords,
   1079     )
   1080 except Exception:

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:964, in DataTree.get_expr(self, expression, engine, allow_native, dtype, with_coords, parser)
    962         dtype = "float32"
    963     result = (
--> [964](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:964)         self.setup_flow({expression: expression}, dtype=dtype)
    965         .load_dataarray()
    966         .isel(expressions=0)
    967     )
    968 else:

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/dataset/__init__.py:426, in DataTree.setup_flow(self, *args, **kwargs)
    425 if regex:
--> [426](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/dataset/__init__.py:426)     raise ValueError(
    427         f"Setup failed for variable {regex.group(1)}.  Check the expression "
    428         f"and the names of the variables in the dataset."
    429     ) from err
    430 else:

ValueError: Setup failed for variable 'util_test'.  Check the expression and the names of the variables in the dataset.

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:942, in DataTree.get_expr(self, expression, engine, allow_native, dtype, with_coords, parser)
    941     else:
--> [942](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:942)         raise KeyError
    943 except (KeyError, IndexError):

KeyError: 

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/dataset/__init__.py:422, in DataTree.setup_flow(self, *args, **kwargs)
    421 try:
--> [422](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/dataset/__init__.py:422)     return super().setup_flow(*args, **kwargs)
    423 except ValueError as err:

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1535, in DataTree.setup_flow(self, definition_spec, cache_dir, name, dtype, boundscheck, error_model, nopython, fastmath, parallel, readme, flow_library, extra_hash_data, write_hash_audit, hashing_level, dim_exclude, with_root_node_name)
   1533 from .flows import Flow
-> [1535](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1535) return Flow(
   1536     self,
   1537     definition_spec,
   1538     cache_dir=cache_dir or self.cache_dir,
   1539     name=name,
   1540     dtype=dtype,
   1541     boundscheck=boundscheck,
   1542     nopython=nopython,
   1543     fastmath=fastmath,
   1544     parallel=parallel,
   1545     readme=readme,
   1546     flow_library=flow_library,
   1547     extra_hash_data=extra_hash_data,
   1548     hashing_level=hashing_level,
   1549     error_model=error_model,
   1550     write_hash_audit=write_hash_audit,
   1551     dim_order=self.dim_order,
   1552     dim_exclude=dim_exclude,
   1553     with_root_node_name=with_root_node_name,
   1554 )

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1053, in Flow.__new__(cls, tree, defs, error_model, cache_dir, name, dtype, boundscheck, nopython, fastmath, parallel, readme, flow_library, extra_hash_data, write_hash_audit, hashing_level, dim_order, dim_exclude, bool_wrapping, with_root_node_name, parallel_irunner, parallel_idotter)
   1052 # otherwise finish normal init
-> [1053](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1053) self.__initialize_2(
   1054     defs,
   1055     error_model=error_model,
   1056     name=name,
   1057     dtype=dtype,
   1058     boundscheck=boundscheck,
   1059     nopython=nopython,
   1060     fastmath=fastmath,
   1061     readme=readme,
   1062     parallel=parallel,
   1063     extra_hash_data=extra_hash_data,
   1064     write_hash_audit=write_hash_audit,
   1065     with_root_node_name=with_root_node_name,
   1066     parallel_idotter=parallel_idotter,
   1067     parallel_irunner=parallel_irunner,
   1068 )
   1069 if flow_library is not None:

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1620, in Flow.__initialize_2(self, defs, error_model, name, dtype, boundscheck, nopython, fastmath, readme, parallel, extra_hash_data, write_hash_audit, with_root_node_name, parallel_irunner, parallel_idotter)
   1619 if self._hashing_level <= 1:
-> [1620](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1620)     func_code, all_name_tokens = self.init_sub_funcs(
   1621         defs,
   1622         error_model=error_model,
   1623         boundscheck=boundscheck,
   1624         nopython=nopython,
   1625         fastmath=fastmath,
   1626     )
   1627     self._func_code = func_code

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1546, in Flow.init_sub_funcs(self, defs, error_model, boundscheck, nopython, fastmath)
   1545     logger.error(f"unable to rewrite '{k}' to itself")
-> [1546](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/flows.py:1546)     raise ValueError(f"unable to rewrite '{k}' to itself")
   1547 logger.debug(f"[{k}] rewrite {init_expr} -> {expr}")

ValueError: unable to rewrite 'util_test' to itself

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
Cell In[20], [line 5](vscode-notebook-cell:?execution_count=20&line=5)
      1 modelname = "tour_mode_choice"
      3 from activitysim.estimation.larch import component_model
----> [5](vscode-notebook-cell:?execution_count=20&line=5) model, data = component_model(
      6     modelname,
      7     edb_directory=f"output/estimation_data_bundle/{modelname}/",
      8     return_data=True,
      9 )

File ~/GitHub/tmp/activitysim/activitysim/estimation/larch/__init__.py:64, in component_model(name, *args, **kwargs)
     62     m = globals().get(f"{name}_model")
     63     if m:
---> [64](https://file+.vscode-resource.vscode-cdn.net/Users/amarin/GitHub/tmp/activitysim/example_estimation/~/GitHub/tmp/activitysim/activitysim/estimation/larch/__init__.py:64)         return m(*args, **kwargs)
     65     raise KeyError(f"no known {name}_model")
     66 else:

File ~/GitHub/tmp/activitysim/activitysim/estimation/larch/mode_choice.py:136, in tour_mode_choice_model(name, edb_directory, return_data)
    131 def tour_mode_choice_model(
    132     name="tour_mode_choice",
    133     edb_directory="output/estimation_data_bundle/{name}/",
    134     return_data=False,
    135 ):
--> [136](https://file+.vscode-resource.vscode-cdn.net/Users/amarin/GitHub/tmp/activitysim/example_estimation/~/GitHub/tmp/activitysim/activitysim/estimation/larch/mode_choice.py:136)     return mode_choice_model(
    137         name=name,
    138         edb_directory=edb_directory,
    139         return_data=return_data,
    140     )

File ~/GitHub/tmp/activitysim/activitysim/estimation/larch/mode_choice.py:110, in mode_choice_model(name, edb_directory, return_data, override_filenames)
    107         model.datatree = d
    108         model.choice_co_code = "override_choice_code"
--> [110](https://file+.vscode-resource.vscode-cdn.net/Users/amarin/GitHub/tmp/activitysim/example_estimation/~/GitHub/tmp/activitysim/activitysim/estimation/larch/mode_choice.py:110) mg = lx.ModelGroup(m.values())
    111 explicit_value_parameters(mg)
    112 apply_coefficients(coefficients, mg)

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/model_group.py:62, in ModelGroup.__init__(self, models, title)
     60 else:
     61     self._submodels.append(model)
---> [62](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/model_group.py:62) self._parameter_bucket.attach_model(model)

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/param_core.py:104, in ParameterBucket.attach_model(self, model, name, agg, unmangle)
    102     model.unmangle(structure_only=True)
    103 elif unmangle:
--> [104](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/param_core.py:104)     model.unmangle()
    105 self._models[name] = model
    107 # collect parameters from the incoming model to be attached, to be able
    108 # to update the bucket's parameters if they are not already present

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/jaxmodel.py:165, in Model.unmangle(self, force, structure_only)
    163 try:
    164     setattr(self, marker, True)
--> [165](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/jaxmodel.py:165)     super().unmangle(force=force, structure_only=structure_only)
    166     for mix in self.mixtures:
    167         mix.prep(self._parameter_bucket)

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/numbamodel.py:1192, in NumbaModel.unmangle(self, force, structure_only)
   1190 if not structure_only:
   1191     if self._dataset is None or force:
-> [1192](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/numbamodel.py:1192)         self.reflow_data_arrays()
   1193     if self._fixed_arrays is None or force:
   1194         self._rebuild_fixed_arrays()

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/jaxmodel.py:177, in Model.reflow_data_arrays(self)
    175 """Reload the internal data_arrays so they are consistent with the datatree."""
    176 if self.compute_engine != "jax":
--> [177](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/jaxmodel.py:177)     return super().reflow_data_arrays()
    179 if self.graph is None:
    180     self._data_arrays = None

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/numbamodel.py:1058, in NumbaModel.reflow_data_arrays(self)
   1055 from .data_arrays import prepare_data
   1057 logger.debug(f"Model.datatree.cache_dir = {datatree.cache_dir}")
-> [1058](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/numbamodel.py:1058) self.dataset, self.dataflows = prepare_data(
   1059     datasource=datatree,
   1060     request=self,
   1061     float_dtype=self.float_dtype,
   1062     cache_dir=datatree.cache_dir,
   1063     flows=self.dataflows,
   1064     make_unused_flows=self.use_streaming,
   1065 )
   1066 if self.use_streaming:
   1067     # when streaming the dataset created above is a vestigial
   1068     # one-case dataset, really we just want the flows, so we
   1069     # get rid of the dataset now
   1070     self._dataset = None

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/data_arrays.py:159, in prepare_data(datasource, request, float_dtype, cache_dir, flows, make_unused_flows)
    157 if "co" in request:
    158     log.debug(f"requested co data: {request['co']}")
--> [159](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/data_arrays.py:159)     model_dataset, flows["co"] = _prep_co(
    160         model_dataset,
    161         datatree_co,
    162         request["co"],
    163         tag="co",
    164         dtype=float_dtype,
    165         cache_dir=cache_dir,
    166         flow=flows.get("co"),
    167     )
    168 if "ca" in request:
    169     log.debug(f"requested ca data: {request['ca']}")

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/data_arrays.py:637, in _prep_co(model_dataset, shared_data_co, vars_co, tag, preserve_vars, dtype, dim_name, cache_dir, flow, use_array_maker, use_eval)
    635     return model_dataset, None
    636 if use_eval:
--> [637](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/model/data_arrays.py:637)     arr = shared_data_co.eval_many(
    638         vars_co, dtype=dtype, result_type="dataarray", with_coords=False
    639     ).values
    640 else:
    641     flowname = flownamer(tag, vars_co, shared_data_co._hash_features())

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1158, in DataTree.eval_many(self, expressions, engine, dtype, result_type, with_coords)
   1156 arrays = {}
   1157 for k, v in expressions.items():
-> [1158](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1158)     a = self.eval(
   1159         v, engine=engine, dtype=dtype, name=k, with_coords=with_coords
   1160     )
   1161     if "expressions" in a.coords:
   1162         a = a.drop_vars("expressions")

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1081, in DataTree.eval(self, expression, engine, dtype, name, with_coords)
   1073         result = self.get_expr(
   1074             expression,
   1075             "numexpr",
   (...)
   1078             with_coords=with_coords,
   1079         )
   1080     except Exception:
-> [1081](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:1081)         result = self.get_expr(
   1082             expression,
   1083             "sharrow",
   1084             allow_native=False,
   1085             dtype=dtype,
   1086             with_coords=with_coords,
   1087         )
   1088 else:
   1089     result = self.get_expr(
   1090         expression,
   1091         engine,
   (...)
   1094         with_coords=with_coords,
   1095     )

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:[948](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/sharrow/relationships.py:948), in DataTree.get_expr(self, expression, engine, allow_native, dtype, with_coords, parser)
    945     if dtype is None:
    946         dtype = "float32"
    947     result = (
--> 948         self.setup_flow({expression: expression}, dtype=dtype)
    949         .load_dataarray()
    950         .isel(expressions=0)
    951     )
    952 elif engine == "numexpr":
    953     import numexpr as ne

File /opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/dataset/__init__.py:[426](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniforge/base/envs/asim-dev/lib/python3.10/site-packages/larch/dataset/__init__.py:426), in DataTree.setup_flow(self, *args, **kwargs)
    424 regex = re.match("^unable to rewrite (.*) to itself$", str(err))
    425 if regex:
--> 426     raise ValueError(
    427         f"Setup failed for variable {regex.group(1)}.  Check the expression "
    428         f"and the names of the variables in the dataset."
    429     ) from err
    430 else:
    431     raise err

ValueError: Setup failed for variable 'util_test'.  Check the expression and the names of the variables in the dataset.

@jpn--
Copy link
Member

jpn-- commented Jun 24, 2025

@asiripanich, this is a sharrow-related error. I'll take a look at it and see if I can figure out what's wrong...

Thanks @dhensle for your response.

I ran into an issue today while trying to add a new utility term to the tour mode choice spec of my tour mode choice EDB. This might be a bug or something that needs to be explained a bit more in the documentation. (However, I didn't have any issues modifying or adding new terms to the auto_ownership model. Despite this minor issue, I'm loving this new capability!)

When I tried to add a new line to my tour mode choice spec file, I kept getting this error:

modelname = "tour_mode_choice"

from activitysim.estimation.larch import component_model

model, data = component_model(
    modelname,
    edb_directory=f"output/estimation_data_bundle/{modelname}/",
    return_data=True,
)

> loading from output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_coefficients.csv
> loading from output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_coefficients_template.csv
> loading spec from output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_SPEC.csv
> loading from output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_values_combined.parquet
> unable to rewrite 'util_test' to itself

I tested this using example_estimation and 17_tour_mode_choice.ipynb from this PR.

I added these following lines:

tour_mode_choice_SPEC.csv

util_test,Drive alone not available for escort tours,1,coef_test,,,,,,,,,,,,,,,,,,,,

tour_mode_choice_coefficients_template.csv

coef_test,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_school_univ,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_school_univ,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork

tour_mode_choice_coefficients.csv

coef_test_eatout_escort_othdiscr_othmaint_shopping_social_work_atwork,0,F
coef_test_school_univ,0,F

Any advice on what I might be doing wrong would be greatly appreciated. 😀

See full error

@jpn--
Copy link
Member

jpn-- commented Jun 26, 2025

@asiripanich, turns out you were doing nothing wrong, you just happened to find a bug -- a couple arguments were missing from the code to re-estimate the mode choice models. I've fixed it, and taken your edits to create an example / unit test of re-estimation on the tour mode choice notebook. If you try again it should work now. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants