├── larch ├── scikit.py ├── math │ └── __init__.py ├── log │ ├── __init__.py │ ├── light.py │ └── normal.py ├── data_services │ ├── h5 │ │ ├── token_manip.py │ │ ├── __init__.py │ │ ├── h5pod │ │ │ ├── __init__.py │ │ │ ├── id0a.py │ │ │ └── idga.py │ │ └── h5util.py │ ├── dbf │ │ └── __init__.py │ ├── exceptions.py │ ├── __init__.py │ └── sqlite │ │ └── __init__.py ├── general_precision.pxi ├── data_warehouse │ ├── MTCwork.h5d │ ├── arc.csv.gz │ ├── MTCwork.csv.gz │ ├── MTCwork.sqlite │ ├── US-STATES.dbf │ ├── optima.csv.gz │ ├── swissmetro.csv.gz │ ├── swissmetro.h5.gz │ ├── exampville_taz.zip │ ├── exampville_skims.omx │ ├── exampville_taz_42.zip │ ├── itinerary_data.csv.gz │ ├── exampville_skims_42.omx │ ├── exampville_tours.csv.gz │ ├── exampville_persons.csv.gz │ ├── exampville_tours_42.csv.gz │ ├── exampville_employment.csv.gz │ ├── exampville_households.csv.gz │ ├── exampville_persons_42.csv.gz │ ├── exampville_demographics.csv.gz │ ├── exampville_employment_42.csv.gz │ ├── exampville_households_42.csv.gz │ ├── exampville_demographics_42.csv.gz │ └── __init__.py ├── doc │ ├── _static │ │ ├── trophy-64.png │ │ ├── larch_favicon.ico │ │ ├── larch_favicon.png │ │ └── elm_doc_style.css │ ├── math │ │ └── agg-choice-variance.png │ ├── data.rst │ ├── machine-learning │ │ ├── index.rst │ │ └── prelearn.rst │ ├── math.rst │ ├── example │ │ ├── exampville.rst │ │ ├── swissmetro.rst │ │ ├── larch_nbval_sanitize.cfg │ │ ├── examples.rst │ │ ├── mtc.rst │ │ ├── itinerary.rst │ │ ├── 020_mtc.rst │ │ ├── 021_mtc.rst │ │ ├── 019_mtc.rst │ │ ├── 024_mtc.rst │ │ ├── 023_mtc.rst │ │ ├── 025_mtc.rst │ │ ├── 003_mtc.rst │ │ ├── 009_mtc.rst │ │ └── 002_mtc.rst │ ├── latentclass.rst │ ├── data-pods.rst │ ├── linear-functions.rst │ ├── model.rst │ ├── compiling.rst │ ├── dataservice.rst │ └── index.rst ├── util │ ├── activitysim │ │ ├── __init__.py │ │ ├── data_maker.py │ │ ├── auto_ownership.py │ │ └── tour_mode_choice.py │ ├── uid.py │ ├── colors.py │ ├── bitmasking.py │ ├── aster.py │ ├── counting.py │ ├── timesize.py │ ├── cache_requests.py │ ├── rate_limiter.py │ ├── naming.py │ ├── signal_dict.py │ ├── __init__.py │ └── png.py ├── torch.py ├── conftest.py ├── model │ ├── __init__.py │ ├── persist_flags.pyx │ ├── parameter_frame.pxd │ ├── persist_flags.pxd │ ├── mnl.pxd │ ├── abstract_model.pxd │ ├── tree_struct.pxd │ ├── fastmath.pxi │ ├── linear.pxd │ ├── tree_struct.pyx │ ├── controller.pxd │ ├── possible_overspec.py │ └── cast.h ├── dataset │ ├── dim_names.py │ └── patch.py ├── general_precision.pxd ├── general_precision.pyx ├── exceptions.py ├── warning.py ├── numba │ ├── __init__.py │ └── fast_mapping.py ├── workspace.py ├── linalg │ └── __init__.py ├── scoring.py └── __init__.py ├── tests ├── test_examples.py ├── __init__.py ├── stored_dataframes │ ├── like_ratio_test.pkl.gz │ ├── parameter_summary_test.pkl.gz │ ├── joint_parameter_summary.pkl.gz │ └── __init__.py ├── test_nl_1 │ ├── test_compute_covariance.csv.gz │ ├── test_compute_utility_array.csv.gz │ └── test_compute_probability_array.csv.gz ├── test_constraints │ ├── test_parameter_summary.csv.gz │ └── test_constraint_parameter_summary_ratios.csv.gz ├── test_dataset.py ├── test_histograms.py ├── test_util_manipulation.py ├── test_doctor.py ├── test_nl_1.py ├── test_data_arrays.py ├── test_qmnl.py └── test_model_group.py ├── .gitattributes ├── book ├── requirements.txt ├── api │ ├── ~data.md │ ├── ~models.md │ ├── dataframes.rst │ ├── nesting.rst │ ├── modelgroup.rst │ ├── datatree.rst │ ├── dataset.rst │ ├── linear.rst │ └── model.rst ├── bibliography.md ├── img │ ├── larch-logo.png │ └── larch_favicon.png ├── example │ ├── legacy │ │ ├── mtc.md │ │ ├── swissmetro.md │ │ ├── itinerary.md │ │ ├── 020_mtc.rst │ │ ├── 021_mtc.rst │ │ ├── 019_mtc.rst │ │ ├── 024_mtc.rst │ │ ├── 023_mtc.rst │ │ ├── 025_mtc.rst │ │ ├── 003_mtc.rst │ │ ├── 009_mtc.rst │ │ └── 002_mtc.rst │ ├── legacy.md │ ├── exampville.rst │ ├── swissmetro.md │ ├── larch_nbval_sanitize.cfg │ ├── mtc.rst │ └── itinerary.rst ├── _build.sh ├── user-guide │ ├── larch-demo-xlsx.jpg │ └── example-data │ │ ├── tiny_points.csv │ │ ├── tiny_idco.csv │ │ └── tiny_idca.csv ├── _static │ ├── larch-book.css │ └── switcher.json ├── references.bib ├── _scripts │ ├── developer_doc_title.py │ └── hide_test_cells.py ├── _toc.yml └── intro.md ├── bump_patch.sh ├── img ├── larch.icns ├── trophy-64.png ├── larch_favicon.ico └── larch_favicon.png ├── .idea ├── encodings.xml ├── codeStyles │ └── codeStyleConfig.xml ├── other.xml ├── modules.xml ├── misc.xml ├── runConfigurations │ ├── Docs.xml │ ├── Sphinx_Test.xml │ └── PyTest.xml ├── Larch.iml └── inspectionProfiles │ └── Project_Default.xml ├── conda-recipe ├── conda_build_config.yaml ├── conda_upload.sh └── meta.yaml ├── bumpversion.cfg ├── .pre-commit-config.yaml ├── pyproject.toml ├── strip_metadata.sh ├── tools ├── get_version.py ├── appveyor │ └── run_with_env.cmd └── gitversion.py ├── environments └── development.yml ├── .travis.yml ├── .gitignore ├── README.rst └── .github └── workflows └── dev-docs.yml /larch/scikit.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /larch/math/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_examples.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | sqlite/* linguist-vendored -------------------------------------------------------------------------------- /larch/log/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | logger_name = "Larch" 3 | -------------------------------------------------------------------------------- /larch/data_services/h5/token_manip.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /book/requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter-book 2 | matplotlib 3 | numpy 4 | -------------------------------------------------------------------------------- /bump_patch.sh: -------------------------------------------------------------------------------- 1 | bumpversion --config-file bumpversion.cfg patch 2 | -------------------------------------------------------------------------------- /larch/data_services/dbf/__init__.py: -------------------------------------------------------------------------------- 1 | from .dbf_reader import DBF 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .test_qmnl import test_saturate_cpu 2 | 3 | -------------------------------------------------------------------------------- /book/api/~data.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | ```{tableofcontents} 4 | ``` 5 | 6 | -------------------------------------------------------------------------------- /book/api/~models.md: -------------------------------------------------------------------------------- 1 | # Models 2 | 3 | ```{tableofcontents} 4 | ``` 5 | 6 | -------------------------------------------------------------------------------- /img/larch.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/img/larch.icns -------------------------------------------------------------------------------- /larch/general_precision.pxi: -------------------------------------------------------------------------------- 1 | 2 | 3 | DEF DOUBLE_PRECISION = True 4 | 5 | -------------------------------------------------------------------------------- /img/trophy-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/img/trophy-64.png -------------------------------------------------------------------------------- /book/bibliography.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | ```{bibliography} 4 | :style: plain 5 | ``` 6 | -------------------------------------------------------------------------------- /book/img/larch-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/book/img/larch-logo.png -------------------------------------------------------------------------------- /img/larch_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/img/larch_favicon.ico -------------------------------------------------------------------------------- /img/larch_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/img/larch_favicon.png -------------------------------------------------------------------------------- /book/example/legacy/mtc.md: -------------------------------------------------------------------------------- 1 | # MTC Mode Choice Examples 2 | 3 | ```{tableofcontents} 4 | ``` 5 | -------------------------------------------------------------------------------- /book/_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | conda info 3 | python _scripts/hide_test_cells.py 4 | jb build . 5 | -------------------------------------------------------------------------------- /book/img/larch_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/book/img/larch_favicon.png -------------------------------------------------------------------------------- /larch/data_warehouse/MTCwork.h5d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/MTCwork.h5d -------------------------------------------------------------------------------- /larch/data_warehouse/arc.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/arc.csv.gz -------------------------------------------------------------------------------- /larch/doc/_static/trophy-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/doc/_static/trophy-64.png -------------------------------------------------------------------------------- /book/user-guide/larch-demo-xlsx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/book/user-guide/larch-demo-xlsx.jpg -------------------------------------------------------------------------------- /larch/data_warehouse/MTCwork.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/MTCwork.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/MTCwork.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/MTCwork.sqlite -------------------------------------------------------------------------------- /larch/data_warehouse/US-STATES.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/US-STATES.dbf -------------------------------------------------------------------------------- /larch/data_warehouse/optima.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/optima.csv.gz -------------------------------------------------------------------------------- /larch/doc/_static/larch_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/doc/_static/larch_favicon.ico -------------------------------------------------------------------------------- /larch/doc/_static/larch_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/doc/_static/larch_favicon.png -------------------------------------------------------------------------------- /larch/util/activitysim/__init__.py: -------------------------------------------------------------------------------- 1 | from .data_maker import * 2 | from .general import * 3 | from .cdap import * 4 | -------------------------------------------------------------------------------- /larch/util/uid.py: -------------------------------------------------------------------------------- 1 | _uidn = 0 2 | 3 | def uid(): 4 | global _uidn 5 | _uidn += 1 6 | return "rx{}".format(_uidn) 7 | -------------------------------------------------------------------------------- /larch/data_warehouse/swissmetro.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/swissmetro.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/swissmetro.h5.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/swissmetro.h5.gz -------------------------------------------------------------------------------- /larch/doc/math/agg-choice-variance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/doc/math/agg-choice-variance.png -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_taz.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_taz.zip -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_skims.omx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_skims.omx -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_taz_42.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_taz_42.zip -------------------------------------------------------------------------------- /larch/data_warehouse/itinerary_data.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/itinerary_data.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_skims_42.omx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_skims_42.omx -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_tours.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_tours.csv.gz -------------------------------------------------------------------------------- /larch/data_services/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class NoKnownShape(ValueError): 4 | pass 5 | 6 | class NoKnownType(ValueError): 7 | pass 8 | -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_persons.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_persons.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_tours_42.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_tours_42.csv.gz -------------------------------------------------------------------------------- /tests/stored_dataframes/like_ratio_test.pkl.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/tests/stored_dataframes/like_ratio_test.pkl.gz -------------------------------------------------------------------------------- /tests/test_nl_1/test_compute_covariance.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/tests/test_nl_1/test_compute_covariance.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_employment.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_employment.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_households.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_households.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_persons_42.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_persons_42.csv.gz -------------------------------------------------------------------------------- /tests/test_nl_1/test_compute_utility_array.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/tests/test_nl_1/test_compute_utility_array.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_demographics.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_demographics.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_employment_42.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_employment_42.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_households_42.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_households_42.csv.gz -------------------------------------------------------------------------------- /tests/stored_dataframes/parameter_summary_test.pkl.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/tests/stored_dataframes/parameter_summary_test.pkl.gz -------------------------------------------------------------------------------- /tests/test_constraints/test_parameter_summary.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/tests/test_constraints/test_parameter_summary.csv.gz -------------------------------------------------------------------------------- /tests/test_nl_1/test_compute_probability_array.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/tests/test_nl_1/test_compute_probability_array.csv.gz -------------------------------------------------------------------------------- /larch/data_warehouse/exampville_demographics_42.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/larch/data_warehouse/exampville_demographics_42.csv.gz -------------------------------------------------------------------------------- /tests/stored_dataframes/joint_parameter_summary.pkl.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/tests/stored_dataframes/joint_parameter_summary.pkl.gz -------------------------------------------------------------------------------- /book/_static/larch-book.css: -------------------------------------------------------------------------------- 1 | #site-navigation h1.site-logo { 2 | font-size: 0.7em; 3 | color: #4D4D4D; 4 | font-weight: 700; 5 | margin-top: 0; 6 | } -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /larch/torch.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | from _larch_torch import * 4 | except ImportError: 5 | import warnings 6 | warnings.warn("larch.torch is not installed") 7 | 8 | -------------------------------------------------------------------------------- /larch/conftest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | 5 | @pytest.fixture(autouse=True) 6 | def add_np(doctest_namespace): 7 | doctest_namespace["np"] = np 8 | -------------------------------------------------------------------------------- /larch/doc/data.rst: -------------------------------------------------------------------------------- 1 | 2 | ====== 3 | Data 4 | ====== 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | 9 | data-fundamentals 10 | data-frames 11 | dataservice 12 | 13 | -------------------------------------------------------------------------------- /tests/test_constraints/test_constraint_parameter_summary_ratios.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpn--/larch/HEAD/tests/test_constraints/test_constraint_parameter_summary_ratios.csv.gz -------------------------------------------------------------------------------- /book/_static/switcher.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "v5.7.2 (latest)", 4 | "version": "5.7.2" 5 | }, 6 | { 7 | "version": "5.4.1" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /conda-recipe/conda_build_config.yaml: -------------------------------------------------------------------------------- 1 | python: 2 | - 3.9 # [not win] 3 | - 3.10 4 | 5 | numpy: 6 | - 1.21.4 7 | 8 | #CONDA_BUILD_SYSROOT: 9 | # - /opt/MacOSX10.9.sdk # [osx] -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /larch/doc/machine-learning/index.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ================ 4 | Machine Learning 5 | ================ 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | machine_learning 11 | prelearn 12 | 13 | -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /book/user-guide/example-data/tiny_points.csv: -------------------------------------------------------------------------------- 1 | caseid,orig_x,orig_y,dest_x,dest_y 2 | 1,946907,925689,884665,494832 3 | 2,952193,945628,892053,478115 4 | 3,889200,491106,959298,919813 5 | 4,877274,476391,947965,925746 6 | -------------------------------------------------------------------------------- /larch/model/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .model import Model 3 | from .systematic_alternatives import SystematicAlternatives 4 | from .linear import DictOfLinearFunction_C, LinearFunction_C 5 | from .persist_flags import * 6 | 7 | -------------------------------------------------------------------------------- /book/user-guide/example-data/tiny_idco.csv: -------------------------------------------------------------------------------- 1 | caseid,Income,CarTime,CarCost,BusTime,BusCost,WalkTime,Chosen 2 | 1,30000,30,150,40,100,20,Car 3 | 2,30000,25,125,35,100,0,Bus 4 | 3,40000,40,125,50,75,30,Walk 5 | 4,50000,15,225,20,150,10,Walk 6 | -------------------------------------------------------------------------------- /larch/dataset/dim_names.py: -------------------------------------------------------------------------------- 1 | # Dense 2 | CASEID = '_caseid_' 3 | ALTID = '_altid_' 4 | 5 | # Sparse 6 | CASEALT = '_casealt_' 7 | ALTIDX = '_alt_idx_' 8 | CASEPTR = '_caseptr_' 9 | GROUPID = '_groupid_' 10 | INGROUP = '_ingroup_' 11 | -------------------------------------------------------------------------------- /larch/general_precision.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | 4 | include "general_precision.pxi" 5 | 6 | IF DOUBLE_PRECISION: 7 | ctypedef double l4_float_t 8 | ELSE: 9 | ctypedef float l4_float_t 10 | 11 | 12 | -------------------------------------------------------------------------------- /larch/data_services/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | _reserved_names_ = {'_VAULT_', '_BUNCHES_'} 3 | 4 | # import numpy 5 | from .pod import Pod 6 | from .podlist import Pods 7 | from .service import DataService 8 | from .h5 import * 9 | from .general import SystematicAlternatives -------------------------------------------------------------------------------- /larch/general_precision.pyx: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | import numpy 4 | 5 | include "general_precision.pxi" 6 | 7 | IF DOUBLE_PRECISION: 8 | l4_float_dtype = numpy.float64 9 | ELSE: 10 | l4_float_dtype = numpy.float64 11 | 12 | -------------------------------------------------------------------------------- /larch/doc/machine-learning/prelearn.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. currentmodule:: larch.prelearning 4 | 5 | =============================== 6 | Using Scikit-Learn within Larch 7 | =============================== 8 | 9 | .. autoclass:: Prelearner 10 | 11 | .. autoclass:: XGBoostPrelearner 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 5.7.2 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:larch/__init__.py] 7 | 8 | [bumpversion:file:conda-recipe/meta.yaml] 9 | 10 | [bumpversion:file:book/_config.yml] 11 | 12 | [bumpversion:file:book/_static/switcher.json] 13 | -------------------------------------------------------------------------------- /larch/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | # Errors 3 | 4 | class MissingDataError(ValueError): 5 | pass 6 | 7 | class DuplicateColumnNames(ValueError): 8 | pass 9 | 10 | class BHHHSimpleStepFailure(RuntimeError): 11 | pass 12 | 13 | # Warnings 14 | 15 | class ParameterNotInModelWarning(UserWarning): 16 | pass 17 | 18 | -------------------------------------------------------------------------------- /book/example/legacy.md: -------------------------------------------------------------------------------- 1 | (deprecated-examples)= 2 | # Legacy Example Models 3 | 4 | Future development of Larch will be on version that sits on numba, xarray, and sharrow, 5 | as that code is faster and more extensible. Examples using the legacy `DataFrames` code 6 | are provided here. 7 | 8 | ```{tableofcontents} 9 | ``` 10 | -------------------------------------------------------------------------------- /book/api/dataframes.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | DataFrames 3 | ========== 4 | 5 | The :ref:`DataFrames` interface for larch remains available for use, but future 6 | development on this package will be on the :ref:`Dataset`/:ref:`DataTree` interface. 7 | 8 | .. autosummary:: 9 | :toctree: generated/ 10 | 11 | larch.DataFrames 12 | -------------------------------------------------------------------------------- /book/user-guide/example-data/tiny_idca.csv: -------------------------------------------------------------------------------- 1 | caseid,altid,Income,Time,Cost,Chosen 2 | 1,Car,30000,30,150,1 3 | 1,Bus,30000,40,100,0 4 | 1,Walk,30000,20,0,0 5 | 2,Car,30000,25,125,0 6 | 2,Bus,30000,35,100,1 7 | 3,Car,40000,40,125,0 8 | 3,Bus,40000,50,75,0 9 | 3,Walk,40000,30,0,1 10 | 4,Car,50000,15,225,0 11 | 4,Bus,50000,20,150,0 12 | 4,Walk,50000,10,0,1 -------------------------------------------------------------------------------- /conda-recipe/conda_upload.sh: -------------------------------------------------------------------------------- 1 | # Only need to change these two variables 2 | PKG_NAME=larch 3 | USER=jpn 4 | 5 | OS=$TRAVIS_OS_NAME-64 6 | mkdir ~/conda-bld 7 | conda config --set anaconda_upload no 8 | export CONDA_BLD_PATH=~/conda-bld 9 | conda build conda-recipe 10 | anaconda -t $CONDA_UPLOAD_TOKEN upload -u $USER $CONDA_BLD_PATH/$OS/$PKG_NAME-*.tar.bz2 --force 11 | -------------------------------------------------------------------------------- /larch/log/light.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | LIGHT_CONSOLE_LOG_FORMAT = '%(message)s' 4 | 5 | from .normal import * 6 | 7 | # Remove timestamps from console stream 8 | 9 | for entry in logger.handlers: 10 | if (isinstance(entry, logging.StreamHandler)) and (entry.formatter._fmt == CONSOLE_LOG_FORMAT): 11 | entry.setFormatter(logging.Formatter(LIGHT_CONSOLE_LOG_FORMAT)) 12 | 13 | -------------------------------------------------------------------------------- /larch/doc/math.rst: -------------------------------------------------------------------------------- 1 | 2 | ==================================== 3 | Mathematics of Logit Choice Modeling 4 | ==================================== 5 | 6 | This documentation will eventually provide instruction on some of the more interesting topics on the underlying 7 | mathematics of logit models. 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | math/aggregate-choice 13 | -------------------------------------------------------------------------------- /book/references.bib: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | @book{koppelman2006self, 4 | title={A self instructing course in mode choice modeling: multinomial and nested logit models}, 5 | author={Koppelman, Frank S and Bhat, Chandra}, 6 | year={2006}, 7 | publisher={FTA US Department of Transportation}, 8 | url={https://www.caee.utexas.edu/prof/bhat/COURSES/LM_Draft_060131Final-060630.pdf} 9 | } 10 | -------------------------------------------------------------------------------- /book/example/exampville.rst: -------------------------------------------------------------------------------- 1 | 2 | ====================== 3 | Exampville Simulator 4 | ====================== 5 | 6 | Exampville is a simulation tool provided with Larch that can quickly simulate the 7 | kind of data that a transportation planner might have available when building 8 | a travel model. 9 | 10 | **Exampville Models** 11 | 12 | .. toctree:: 13 | :glob: 14 | 15 | 2* 16 | 17 | -------------------------------------------------------------------------------- /larch/doc/example/exampville.rst: -------------------------------------------------------------------------------- 1 | 2 | ====================== 3 | Exampville Simulator 4 | ====================== 5 | 6 | Exampville is a simulation tool provided with Larch that can quickly simulate the 7 | kind of data that a transportation planner might have available when building 8 | a travel model. 9 | 10 | **Exampville Models** 11 | 12 | .. toctree:: 13 | :glob: 14 | 15 | 2* 16 | 17 | -------------------------------------------------------------------------------- /larch/warning.py: -------------------------------------------------------------------------------- 1 | 2 | import warnings 3 | import contextlib 4 | 5 | @contextlib.contextmanager 6 | def ignore_warnings(): 7 | with warnings.catch_warnings(): 8 | warnings.simplefilter("ignore") 9 | yield 10 | 11 | 12 | class NoDefaultValueWarning(Warning): 13 | pass 14 | 15 | def no_default_value(message): 16 | #warnings.warn(message, NoDefaultValueWarning, stacklevel=2) 17 | pass 18 | 19 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.1.0 5 | hooks: 6 | - id: check-yaml 7 | exclude: conda-recipe/meta.yaml 8 | - id: end-of-file-fixer 9 | exclude: .*\.ipynb|bumpversion.cfg 10 | - id: trailing-whitespace 11 | 12 | - repo: https://github.com/kynan/nbstripout 13 | rev: 0.5.0 14 | hooks: 15 | - id: nbstripout 16 | -------------------------------------------------------------------------------- /larch/model/persist_flags.pyx: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | 4 | __all__ = ( 5 | "PERSIST_UTILITY", 6 | "PERSIST_EXP_UTILITY", 7 | "PERSIST_PROBABILITY", 8 | "PERSIST_COND_LOG_PROB", 9 | "PERSIST_LOGLIKE_CASEWISE", 10 | "PERSIST_D_UTILITY", 11 | "PERSIST_D_PROBABILITY", 12 | "PERSIST_D_LOGLIKE_CASEWISE", 13 | "PERSIST_BHHH", 14 | "PERSIST_PARTS", 15 | "PERSIST_ALL", 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /book/api/nesting.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | NestingTree 3 | =========== 4 | 5 | .. currentmodule:: larch.model.tree 6 | 7 | 8 | .. autosummary:: 9 | :toctree: generated/ 10 | 11 | NestingTree 12 | 13 | Methods 14 | ======= 15 | 16 | .. autosummary:: 17 | :toctree: generated/ 18 | 19 | NestingTree.new_node 20 | NestingTree.add_node 21 | NestingTree.remove_node 22 | NestingTree.add_edge 23 | NestingTree.remove_edge 24 | -------------------------------------------------------------------------------- /tests/test_dataset.py: -------------------------------------------------------------------------------- 1 | import larch.numba as lx 2 | import pytest 3 | from pytest import approx 4 | 5 | def test_dataset(): 6 | d = lx.examples.MTC(format='dataset') 7 | 8 | assert d.dc.CASEID == 'caseid' 9 | assert d.dc.ALTID == 'altid' 10 | 11 | assert d.dc.chose.dc.CASEID == 'caseid' 12 | assert d.dc.chose.dc.ALTID == 'altid' 13 | 14 | assert d.dc['chose'].dc.CASEID == 'caseid' 15 | assert d.dc['chose'].dc.ALTID == 'altid' 16 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /book/example/swissmetro.md: -------------------------------------------------------------------------------- 1 | 2 | # Swissmetro Examples 3 | 4 | **Example Data** 5 | 6 | The Swissmetro sample dataset is the same data used in the examples for [Biogeme](http://biogeme.epfl.ch/). 7 | Note that Larch and Biogeme have been developed for different applicationsa and have different capabilities. 8 | It is not possible to reproduce using Larch every example model shown for Biogeme. 9 | 10 | **Example Models** 11 | 12 | ```{tableofcontents} 13 | ``` 14 | -------------------------------------------------------------------------------- /book/example/legacy/swissmetro.md: -------------------------------------------------------------------------------- 1 | 2 | # Swissmetro Examples 3 | 4 | **Example Data** 5 | 6 | The Swissmetro sample dataset is the same data used in the examples for [Biogeme](http://biogeme.epfl.ch/). 7 | Note that Larch and Biogeme have been developed for different applicationsa and have different capabilities. 8 | It is not possible to reproduce using Larch every example model shown for Biogeme. 9 | 10 | **Example Models** 11 | 12 | ```{tableofcontents} 13 | ``` 14 | -------------------------------------------------------------------------------- /larch/model/parameter_frame.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | from ..general_precision cimport * 4 | 5 | cdef class ParameterFrame: 6 | 7 | cdef public: 8 | object _frame # pandas.DataFrame 9 | object _matrixes # Dict[ndarray] 10 | 11 | cdef: 12 | unicode _title 13 | bint _mangled 14 | object _prior_frame_values # pandas.Series 15 | object _display_order 16 | object _display_order_tail 17 | 18 | -------------------------------------------------------------------------------- /larch/model/persist_flags.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | cpdef enum: 4 | PERSIST_UTILITY = 0x1 5 | PERSIST_EXP_UTILITY = 0x2 6 | PERSIST_PROBABILITY = 0x4 7 | PERSIST_LOGLIKE_CASEWISE = 0x8 8 | 9 | PERSIST_D_UTILITY = 0x10 10 | PERSIST_COND_LOG_PROB = 0x20 11 | PERSIST_D_PROBABILITY = 0x40 12 | PERSIST_D_LOGLIKE_CASEWISE = 0x80 13 | 14 | PERSIST_BHHH = 0x100 15 | PERSIST_PARTS = 0x200 16 | PERSIST_QUANTITY = 0x400 17 | 18 | PERSIST_ALL = 0xFFFF 19 | -------------------------------------------------------------------------------- /larch/model/mnl.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | from ..general_precision cimport * 4 | 5 | cdef l4_float_t _mnl_log_likelihood_from_probability( 6 | int n_alts, 7 | l4_float_t* probability, # input [n_alts] 8 | l4_float_t* choice, # input [n_alts] 9 | ) nogil 10 | 11 | cdef l4_float_t _mnl_log_likelihood_from_probability_stride( 12 | int n_alts, 13 | l4_float_t[:] probability, # input [n_alts] 14 | l4_float_t[:] choice, # input [n_alts] 15 | ) nogil 16 | -------------------------------------------------------------------------------- /tests/stored_dataframes/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def stable_df(df, filename): 4 | import os, pandas 5 | from pandas.io.formats.style import Styler 6 | if isinstance(df, Styler): 7 | df = df.data 8 | f = os.path.join(os.path.dirname(__file__), f'{filename}.pkl.gz') 9 | if os.path.exists(f): 10 | comparison = pandas.read_pickle(f) 11 | pandas.testing.assert_frame_equal(df, comparison) 12 | else: 13 | try: 14 | df.to_pickle(f) 15 | except FileNotFoundError: 16 | raise FileNotFoundError(f) 17 | 18 | -------------------------------------------------------------------------------- /larch/data_services/sqlite/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import apsw 3 | import numpy 4 | import pandas 5 | 6 | try: 7 | from .sqlite_arrays import _sqlite_array_2d_float64 8 | except ImportError: 9 | _sqlite_array_2d_float64 = None 10 | 11 | class Connection(apsw.Connection): 12 | 13 | def read_data(self, query, dtype=numpy.float64, index_col=None): 14 | if dtype==numpy.float64: 15 | return _sqlite_array_2d_float64(self.sqlite3pointer(), query, index_col=index_col) 16 | raise TypeError("cannot read in {}".format(dtype)) 17 | -------------------------------------------------------------------------------- /larch/doc/example/swissmetro.rst: -------------------------------------------------------------------------------- 1 | 2 | ====================== 3 | Swissmetro Examples 4 | ====================== 5 | 6 | **Example Data** 7 | 8 | The Swissmetro sample dataset is the same data used in the examples for `Biogeme `_. 9 | Note that Larch and Biogeme have been developed for different applicationsa and have different capabilities. 10 | It is not possible to reproduce using Larch every example model shown for Biogeme. 11 | **Example Models** 12 | 13 | .. toctree:: 14 | :glob: 15 | 16 | 1* 17 | 18 | -------------------------------------------------------------------------------- /larch/model/abstract_model.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | from ..general_precision cimport * 4 | from .parameter_frame cimport ParameterFrame 5 | 6 | 7 | 8 | cdef class AbstractChoiceModel(ParameterFrame): 9 | 10 | cdef public: 11 | 12 | object _most_recent_estimation_result 13 | object _possible_overspecification 14 | 15 | double _cached_loglike_null 16 | double _cached_loglike_nil 17 | double _cached_loglike_constants_only 18 | double _cached_loglike_best 19 | 20 | object _dashboard 21 | 22 | -------------------------------------------------------------------------------- /book/example/larch_nbval_sanitize.cfg: -------------------------------------------------------------------------------- 1 | [regex1] 2 | regex: \d{1,2}/\d{1,2}/\d{2,4} 3 | replace: DATE-STAMP 4 | 5 | [regex2] 6 | regex: \d{2}:\d{2}:\d{2} 7 | replace: TIME-STAMP 8 | 9 | [regex3] 10 | regex: \d{2}:\d{2}:\d{2}.\d{6} 11 | replace: ELAPSED-TIME-STAMP 12 | 13 | [regex4] 14 | regex: \d{1}:\d{2}:\d{2}.\d{6} 15 | replace: ELAPSED-TIME-STAMP 16 | 17 | [regex5] 18 | regex: seconds=\d{2}, microseconds=\d{6} 19 | replace: SECONDS-MICROSECONDS 20 | 21 | [regex6] 22 | regex:

Iteration [0-9]* .*<\/h3> 23 | replace: ITERATION-HEADER 24 | 25 | -------------------------------------------------------------------------------- /larch/doc/example/larch_nbval_sanitize.cfg: -------------------------------------------------------------------------------- 1 | [regex1] 2 | regex: \d{1,2}/\d{1,2}/\d{2,4} 3 | replace: DATE-STAMP 4 | 5 | [regex2] 6 | regex: \d{2}:\d{2}:\d{2} 7 | replace: TIME-STAMP 8 | 9 | [regex3] 10 | regex: \d{2}:\d{2}:\d{2}.\d{6} 11 | replace: ELAPSED-TIME-STAMP 12 | 13 | [regex4] 14 | regex: \d{1}:\d{2}:\d{2}.\d{6} 15 | replace: ELAPSED-TIME-STAMP 16 | 17 | [regex5] 18 | regex: seconds=\d{2}, microseconds=\d{6} 19 | replace: SECONDS-MICROSECONDS 20 | 21 | [regex6] 22 | regex:

Iteration [0-9]* .*<\/h3> 23 | replace: ITERATION-HEADER 24 | 25 | -------------------------------------------------------------------------------- /book/_scripts/developer_doc_title.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | import sys 4 | 5 | from ruamel.yaml import YAML 6 | 7 | config_file = Path( 8 | os.path.join( 9 | os.path.dirname(__file__), 10 | "..", 11 | "_config.yml", 12 | ) 13 | ) 14 | 15 | if len(sys.argv) >= 2: 16 | title = sys.argv[1] 17 | else: 18 | title = "DEVELOPMENT DOCS" 19 | yaml = YAML(typ="rt") # default, if not specfied, is 'rt' (round-trip) 20 | content = yaml.load(config_file) 21 | content["title"] = title 22 | yaml.dump(content, config_file) 23 | -------------------------------------------------------------------------------- /book/api/modelgroup.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | ModelGroup 3 | =========== 4 | 5 | .. currentmodule:: larch.model.model_group 6 | 7 | 8 | .. autosummary:: 9 | :toctree: generated/ 10 | 11 | ModelGroup 12 | 13 | 14 | Attributes 15 | ========== 16 | 17 | .. autosummary:: 18 | :toctree: generated/ 19 | 20 | ModelGroup.n_cases 21 | 22 | 23 | Methods 24 | ======= 25 | 26 | .. autosummary:: 27 | :toctree: generated/ 28 | 29 | ModelGroup.set_values 30 | ModelGroup.total_weight 31 | ModelGroup.loglike 32 | ModelGroup.loglike2 33 | ModelGroup.to_xlsx 34 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel", 5 | "oldest-supported-numpy", 6 | "Cython", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | 11 | [tool.pytest.ini_options] 12 | minversion = "6.0" 13 | addopts = """\ 14 | -v --nbmake --disable-warnings \ 15 | --doctest-modules \ 16 | --ignore=sandbox.py \ 17 | --ignore=larch/util/visual_processing.py \ 18 | --ignore-glob=book/_build/* \ 19 | """ 20 | testpaths = [ 21 | "tests", 22 | "book/example", 23 | ] 24 | doctest_optionflags = "NORMALIZE_WHITESPACE" 25 | -------------------------------------------------------------------------------- /strip_metadata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | eval "$(conda shell.bash hook)" 4 | conda activate garage37 5 | 6 | nbstripout larch/doc/example/000_mtc_data.ipynb --keep-output --keep-count 7 | nbstripout larch/doc/example/200_exampville.ipynb --keep-output --keep-count 8 | nbstripout larch/doc/example/201_exville_mode_choice.ipynb --keep-output --keep-count 9 | nbstripout larch/doc/example/202_exville_mc_logsums.ipynb --keep-output --keep-count 10 | nbstripout larch/doc/example/203_exville_dest.ipynb --keep-output --keep-count 11 | nbstripout larch/doc/example/300_itinerary.ipynb --keep-output --keep-count 12 | -------------------------------------------------------------------------------- /larch/doc/example/examples.rst: -------------------------------------------------------------------------------- 1 | 2 | .. currentmodule:: larch.examples 3 | 4 | .. _examples: 5 | 6 | ======== 7 | Examples 8 | ======== 9 | 10 | Here we provide some illustrative examples. 11 | You can reproduce all of these examples in your own install of Larch, 12 | as all of the example data is included with the standard distribution. 13 | To work with any objects you find in the examples, a handy function 14 | is provided to extract that object directly into your workspace: 15 | 16 | .. autofunction:: larch.example 17 | 18 | 19 | .. toctree:: 20 | 21 | mtc 22 | swissmetro 23 | exampville 24 | itinerary 25 | 26 | -------------------------------------------------------------------------------- /larch/util/colors.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | _color_rgb256 = {} 5 | _color_rgb256['sky'] = (35,192,241) 6 | _color_rgb256['ocean'] = (29,139,204) 7 | _color_rgb256['night'] = (100,120,186) 8 | _color_rgb256['forest'] = (39,182,123) 9 | _color_rgb256['lime'] = (128,189,1) 10 | _color_rgb256['orange'] = (246,147,0) 11 | _color_rgb256['red'] = (246,1,0) 12 | 13 | def hexcolor(color): 14 | c = _color_rgb256[color.casefold()] 15 | return "#{}{}{}".format(*(hex(c[i])[-2:] if c[i]>15 else "0"+hex(c[i])[-1:] for i in range(3))) 16 | 17 | def strcolor_rgb256(color): 18 | c = _color_rgb256[color.casefold()] 19 | return "{},{},{}".format(*c) 20 | -------------------------------------------------------------------------------- /larch/numba/__init__.py: -------------------------------------------------------------------------------- 1 | from .model import NumbaModel as Model 2 | from .. import DataFrames, P, X, PX, OMX, DBF, Reporter, NumberedCaption, read_metadata, examples, util, __version__ 3 | from ..examples import example as _example 4 | from ..data_warehouse import example_file 5 | from ..model.model_group import ModelGroup 6 | 7 | try: 8 | from .. import dataset 9 | from ..dataset import Dataset, DataArray, DataTree, merge 10 | except RuntimeError: 11 | pass 12 | 13 | def example(*args, **kwargs): 14 | import importlib 15 | kwargs['larch'] = importlib.import_module(__name__) 16 | return _example(*args, **kwargs) 17 | -------------------------------------------------------------------------------- /tools/get_version.py: -------------------------------------------------------------------------------- 1 | import os 2 | import setuptools_scm 3 | import sys 4 | 5 | def get_version(): 6 | version = setuptools_scm.get_version(root='..', relative_to=__file__) 7 | os.environ["LARCH_VERSION"] = version 8 | if len(sys.argv) >= 2: 9 | v_file = sys.argv[1] 10 | else: 11 | v_file = os.path.normpath( 12 | os.path.join(os.path.dirname(__file__), "..", "LARCH_VERSION.txt") 13 | ) 14 | with open(v_file, 'wt') as f: 15 | f.write(f"LARCH_VERSION={version}\n") 16 | print(f"LARCH_VERSION={version}") 17 | return version 18 | 19 | if __name__ == '__main__': 20 | get_version() 21 | -------------------------------------------------------------------------------- /larch/workspace.py: -------------------------------------------------------------------------------- 1 | 2 | from xmle import Reporter, NumberedCaption 3 | 4 | def make_reporter(title, notes=True, viz=True): 5 | """ 6 | Make a standard reporter. 7 | """ 8 | 9 | REPORT = Reporter(title) 10 | REPORT.MAIN = REPORT.section("Estimated Model", "Estimated Model") 11 | if notes is True: 12 | REPORT.NOTES = REPORT.section("Model Notes", "Notes") 13 | elif notes: 14 | REPORT.NOTES = REPORT.section(notes, notes) 15 | if viz is True: 16 | REPORT.VIZ = REPORT.section("Model Visualization", "Visualization") 17 | elif viz: 18 | REPORT.VIZ = REPORT.section(viz, viz) 19 | 20 | REPORT.FIG = NumberedCaption('Figure') 21 | REPORT.TAB = NumberedCaption('Table') 22 | 23 | return REPORT 24 | 25 | -------------------------------------------------------------------------------- /larch/linalg/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def general_inverse(a, small_eig=0.0001): 4 | """Find the matrix inverse if possible, otherwise find the pseudo-inverse.""" 5 | import numpy 6 | if not numpy.isfinite(a).all(): 7 | raise ValueError("nonfinite values in array") 8 | min_eig = None 9 | try: 10 | eig = numpy.linalg.eigvalsh(a) 11 | min_eig = numpy.min(numpy.abs(eig)) 12 | if min_eig < small_eig: 13 | raise numpy.linalg.linalg.LinAlgError() 14 | x = numpy.linalg.inv(a) 15 | except numpy.linalg.linalg.LinAlgError: 16 | if min_eig is not None and min_eig < small_eig: 17 | import warnings 18 | warnings.warn(f"minimum eig {min_eig} in general_inverse") 19 | import scipy.linalg 20 | x = scipy.linalg.pinvh(a) 21 | return x 22 | 23 | 24 | -------------------------------------------------------------------------------- /book/api/datatree.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ======== 4 | DataTree 5 | ======== 6 | 7 | .. autosummary:: 8 | :toctree: generated/ 9 | 10 | DataTree 11 | 12 | 13 | Attributes 14 | ---------- 15 | 16 | .. autosummary:: 17 | :toctree: generated/ 18 | 19 | DataTree.root_node_name 20 | DataTree.subspaces 21 | DataTree.relationships_are_digitized 22 | DataTree.n_cases 23 | DataTree.n_alts 24 | DataTree.CASEID 25 | DataTree.ALTID 26 | 27 | 28 | Methods 29 | ------- 30 | 31 | .. autosummary:: 32 | :toctree: generated/ 33 | 34 | DataTree.add_dataset 35 | DataTree.add_relationship 36 | DataTree.digitize_relationships 37 | DataTree.query_cases 38 | DataTree.replace_datasets 39 | DataTree.setup_flow 40 | -------------------------------------------------------------------------------- /tests/test_histograms.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | import matplotlib 5 | from matplotlib.testing.decorators import image_comparison 6 | import matplotlib.pyplot as plt 7 | 8 | # @image_comparison(baseline_images=['histogram'], 9 | # extensions=['png']) 10 | # def test_spines_axes_positions(): 11 | # # SF bug 2852168 12 | # fig = plt.figure() 13 | # x = np.linspace(0,2*np.pi,100) 14 | # y = 2*np.sin(x) 15 | # ax = fig.add_subplot(1,1,1) 16 | # ax.set_title('centered spines') 17 | # ax.plot(x,y) 18 | # ax.spines['right'].set_position(('axes',0.1)) 19 | # ax.yaxis.set_ticks_position('right') 20 | # ax.spines['top'].set_position(('axes',0.25)) 21 | # ax.xaxis.set_ticks_position('top') 22 | # ax.spines['left'].set_color('none') 23 | # ax.spines['bottom'].set_color('none') 24 | # 25 | -------------------------------------------------------------------------------- /larch/model/tree_struct.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | from ..general_precision cimport * 4 | 5 | 6 | cdef class TreeStructure: 7 | 8 | cdef: 9 | int n_nodes 10 | int n_elementals 11 | l4_float_t[:] model_mu_param_values # [n_nodes] 12 | #l4_float_t[:,:] model_alpha_param_values # [n_nodes, n_nodes] 13 | int[:] model_mu_param_slots # [n_nodes] 14 | 15 | int n_edges 16 | int[:] edge_dn # [n_edges] 17 | int[:] edge_up # [n_edges] 18 | l4_float_t[:] edge_alpha_values # [n_edges] 19 | l4_float_t[:] edge_logalpha_values # [n_edges] 20 | int[:] first_edge_for_up # [n_nodes] index of first edge where this node is the up 21 | int[:] n_edges_for_up # [n_nodes] n edge where this node is the up 22 | -------------------------------------------------------------------------------- /larch/model/fastmath.pxi: -------------------------------------------------------------------------------- 1 | 2 | 3 | # from libc.math cimport exp, log, pow 4 | # from numpy.math cimport expf, logf 5 | 6 | cdef extern from "fastexp.h" nogil: 7 | float fastexp (float p) 8 | float fasterexp (float p) 9 | 10 | cdef extern from "fastlog.h" nogil: 11 | float fastlog (float p) 12 | float fasterlog (float p) 13 | 14 | cdef inline float f_exp(float x) nogil: 15 | return fastexp(x) 16 | 17 | cdef inline float f_log(float x) nogil: 18 | return fastlog(x) 19 | 20 | 21 | 22 | cdef inline double exp_fast2(double x): 23 | x = 1.0 + x / 1024 24 | x *= x 25 | x *= x 26 | x *= x 27 | x *= x 28 | x *= x 29 | x *= x 30 | x *= x 31 | x *= x 32 | x *= x 33 | x *= x 34 | return x 35 | 36 | cdef inline double exp_fast3(double x): 37 | x = 1.0 + x / 256.0 38 | x *= x 39 | x *= x 40 | x *= x 41 | x *= x 42 | x *= x 43 | x *= x 44 | x *= x 45 | x *= x 46 | return x 47 | -------------------------------------------------------------------------------- /tests/test_util_manipulation.py: -------------------------------------------------------------------------------- 1 | import pandas 2 | import os 3 | import gzip 4 | import pickle 5 | import base64 6 | import io 7 | import numpy 8 | from pytest import approx 9 | from larch.util.data_manipulation import periodize 10 | 11 | def test_periodize(): 12 | h = pandas.Series(range(21)) 13 | p = periodize(h, default='OP', AM=(6.5, 9), PM=(16, 19)) 14 | assert p.dtype == 'category' 15 | assert p[0] == 'OP' 16 | assert p[1] == 'OP' 17 | assert p[2] == 'OP' 18 | assert p[3] == 'OP' 19 | assert p[4] == 'OP' 20 | assert p[5] == 'OP' 21 | assert p[6] == 'OP' 22 | assert p[7] == 'AM' 23 | assert p[8] == 'AM' 24 | assert p[9] == 'AM' 25 | assert p[10] == 'OP' 26 | assert p[11] == 'OP' 27 | assert p[12] == 'OP' 28 | assert p[13] == 'OP' 29 | assert p[14] == 'OP' 30 | assert p[15] == 'OP' 31 | assert p[16] == 'PM' 32 | assert p[17] == 'PM' 33 | assert p[18] == 'PM' 34 | assert p[19] == 'PM' 35 | assert p[20] == 'OP' 36 | -------------------------------------------------------------------------------- /tests/test_doctor.py: -------------------------------------------------------------------------------- 1 | import larch, pytest 2 | from larch import P,X,PX 3 | from pytest import approx, raises 4 | import numpy 5 | import pandas 6 | 7 | def test_nan_weight(): 8 | m = larch.Model.Example(1, legacy=True) 9 | m.weight_co_var = 'hhowndum' 10 | m.load_data() 11 | m.dataframes.data_wt = m.dataframes.data_wt.div(m.dataframes.data_wt) 12 | 13 | m, diagnosis = m.doctor() 14 | assert type(m) is larch.Model 15 | assert 'nan_weight' in diagnosis 16 | assert diagnosis.nan_weight['n'].iloc[0] == 1882 17 | 18 | m, diagnosis = m.doctor(repair_nan_wt=True) 19 | assert type(m) is larch.Model 20 | assert 'nan_weight' in diagnosis 21 | assert diagnosis.nan_weight['n'].iloc[0] == 1882 22 | 23 | m, diagnosis = m.doctor() 24 | assert type(m) is larch.Model 25 | assert 'nan_weight' not in diagnosis 26 | assert len(diagnosis) == 0 27 | 28 | def test_doctor_before_load_data(): 29 | 30 | with raises(ValueError): 31 | larch.Model().doctor() 32 | -------------------------------------------------------------------------------- /larch/model/linear.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | from ..general_precision cimport * 4 | 5 | cdef class UnicodeRef_C(unicode): 6 | pass 7 | 8 | cdef class Ref_Gen: 9 | cdef: 10 | object _kind 11 | 12 | cdef class ParameterRef_C(UnicodeRef_C): 13 | cdef public unicode _formatting 14 | 15 | cdef class DataRef_C(UnicodeRef_C): 16 | pass 17 | 18 | cdef class LinearComponent_C: 19 | cdef: 20 | unicode _param 21 | unicode _data 22 | l4_float_t _scale 23 | 24 | 25 | cdef class LinearFunction_C: 26 | cdef: 27 | object _func 28 | object _instance 29 | unicode name 30 | unicode private_name 31 | 32 | 33 | cdef class DictOfLinearFunction_C: 34 | cdef: 35 | object _map 36 | object _instance 37 | object _alts_validator 38 | unicode name 39 | unicode private_name 40 | 41 | 42 | cdef class GenericContainerCy: 43 | cdef public: 44 | LinearFunction_C _lf 45 | DictOfLinearFunction_C _dlf 46 | object ident -------------------------------------------------------------------------------- /book/example/mtc.rst: -------------------------------------------------------------------------------- 1 | 2 | =============== 3 | MTC Examples 4 | =============== 5 | 6 | **Example Data** 7 | 8 | The MTC sample dataset is the same data used in the 9 | `Self Instructing Manual `_ 10 | for discrete choice modeling:: 11 | 12 | The San Francisco Bay Area work mode choice data set comprises 5029 home-to-work commute trips in the 13 | San Francisco Bay Area. The data is drawn from the San Francisco Bay Area Household Travel Survey 14 | conducted by the Metropolitan Transportation Commission (MTC) in the spring and fall of 1990. This 15 | survey included a one day travel diary for each household member older than five years and detailed 16 | individual and household socio-demographic information. 17 | 18 | You can use this data to reproduce the example models in that 19 | document, and several examples are provided here to do so. 20 | 21 | **Example Models** 22 | 23 | .. toctree:: 24 | :glob: 25 | 26 | 0* 27 | -------------------------------------------------------------------------------- /larch/util/activitysim/data_maker.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | 5 | def make_estimation_data(directory="test"): 6 | if os.path.exists(directory): 7 | check_file = os.path.join(directory, ".gitignore") 8 | if not os.path.exists(check_file): 9 | import importlib 10 | asim_create = importlib.import_module("activitysim.cli.create") 11 | asim_create.get_example("example_estimation_sf", directory) 12 | os.chdir(directory) 13 | cp = subprocess.run( 14 | [ 15 | "activitysim", 16 | "run", 17 | "-c", 18 | "configs_estimation/configs", 19 | "-c", 20 | "configs", 21 | "-o", 22 | "output", 23 | "-d", 24 | "data_sf", 25 | ], 26 | capture_output=True, 27 | ) 28 | with open(check_file, 'rt') as f: 29 | f.write("**/*.csv\n") 30 | f.write("**/*.txt\n") 31 | f.write("**/*.yaml\n") 32 | f.write("**/.gitignore\n") 33 | else: 34 | print(f"using existing directory `{directory}`") 35 | os.chdir(directory) 36 | -------------------------------------------------------------------------------- /larch/doc/example/mtc.rst: -------------------------------------------------------------------------------- 1 | 2 | =============== 3 | MTC Examples 4 | =============== 5 | 6 | **Example Data** 7 | 8 | The MTC sample dataset is the same data used in the 9 | `Self Instructing Manual `_ 10 | for discrete choice modeling:: 11 | 12 | The San Francisco Bay Area work mode choice data set comprises 5029 home-to-work commute trips in the 13 | San Francisco Bay Area. The data is drawn from the San Francisco Bay Area Household Travel Survey 14 | conducted by the Metropolitan Transportation Commission (MTC) in the spring and fall of 1990. This 15 | survey included a one day travel diary for each household member older than five years and detailed 16 | individual and household socio-demographic information. 17 | 18 | You can use this data to reproduce the example models in that 19 | document, and several examples are provided here to do so. 20 | 21 | **Example Models** 22 | 23 | .. toctree:: 24 | :glob: 25 | 26 | 0* 27 | -------------------------------------------------------------------------------- /larch/model/tree_struct.pyx: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | import numpy 4 | 5 | cdef class TreeStructure: 6 | 7 | def __init__(self, model, graph): 8 | self.n_nodes = len(graph) 9 | self.n_elementals = graph.n_elementals() 10 | mu, muslots, up, dn, num, start, val = graph._get_simple_mu_and_alpha(model) 11 | self.model_mu_param_values = mu # [n_nodes] 12 | self.model_mu_param_slots = muslots # [n_nodes] 13 | # self.model_alpha_param_values = alpha # [n_nodes,n_nodes] 14 | self.n_edges = dn.shape[0] # 15 | self.edge_dn = dn # [n_edges] 16 | self.edge_up = up # [n_edges] 17 | self.edge_alpha_values = val # [n_edges] 18 | self.edge_logalpha_values = numpy.log(val) # [n_edges] 19 | self.first_edge_for_up = start # [n_nodes] index of first edge where this node is the up 20 | self.n_edges_for_up = num # [n_nodes] n edge where this node is the up 21 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Docs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | -------------------------------------------------------------------------------- /larch/doc/latentclass.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch.model.latentclass 2 | 3 | ======================= 4 | Latent Class Models 5 | ======================= 6 | 7 | Larch is able to handle latent class models. A latent class model can be thought of 8 | as a two-stage model: first, to which class does an observation belong; and then, given 9 | membership in that class, what are the choice probabilities. Each class can have its 10 | own distinct choice model, and parameters can be either independent or shared across 11 | classes. 12 | 13 | The class membership model is a choice model where "class membership" is the implied 14 | choice, instead of the actual alternatives in the choice set. 15 | 16 | Because the class membership model is not about the choices, it cannot use data about 17 | the choices in the model definition -- the class membership model can be based only on 18 | the `co` data. 19 | 20 | A :class:`LatentClassModel` is the core object used to represent a latent class model. 21 | 22 | .. autoclass:: LatentClassModel 23 | 24 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Sphinx_Test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | -------------------------------------------------------------------------------- /larch/model/controller.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3, embedsignature=True 2 | 3 | from ..general_precision cimport * 4 | from ..dataframes cimport DataFrames 5 | from .linear cimport LinearFunction_C, DictOfLinearFunction_C 6 | from .abstract_model cimport AbstractChoiceModel 7 | from .tree_struct cimport TreeStructure 8 | 9 | cdef class Model5c(AbstractChoiceModel): 10 | 11 | cdef public: 12 | DictOfLinearFunction_C _utility_co 13 | LinearFunction_C _utility_ca 14 | LinearFunction_C _quantity_ca 15 | DataFrames _dataframes 16 | object _dataservice 17 | 18 | cdef: 19 | object _quantity_scale 20 | object _logsum_parameter 21 | object rename_parameters 22 | 23 | str _choice_ca_var 24 | object _choice_co_vars 25 | str _choice_co_code 26 | bint _choice_any 27 | str _weight_co_var 28 | str _availability_var 29 | object _availability_co_vars 30 | bint _availability_any 31 | 32 | bint _is_clone 33 | bint _does_not_require_choice 34 | 35 | object _graph 36 | TreeStructure _tree_struct 37 | 38 | int _n_threads 39 | 40 | -------------------------------------------------------------------------------- /book/example/legacy/itinerary.md: -------------------------------------------------------------------------------- 1 | 2 | # Itinerary Choice Examples 3 | 4 | **Example Data** 5 | 6 | The example itinerary choice bundled with Larch is based on data derived from a ticketing database 7 | provided by the Airlines Reporting Corporation. The data represent ten origin destination 8 | pairs for travel in U.S. continental markets in May of 2013. Itinerary characteristics 9 | have been masked, e.g., carriers are labeled generically as "carrier X" and departure times 10 | have been aggregated into categories. A fare is provided but is not completely accurate (a 11 | random error has been added to each fare). These modifications were made to satisfy 12 | nondisclosure agreements, so that the data can be published freely for teaching and 13 | demostration purposes. It is generally representative of real itinerary choice data used 14 | in practice, and the results obtained from this data are intuitive from a behavioral 15 | perspective, but it is not quite accurate and should not be used for behavioral studies. 16 | 17 | **Example Models** 18 | 19 | ```{tableofcontents} 20 | ``` 21 | -------------------------------------------------------------------------------- /larch/util/bitmasking.py: -------------------------------------------------------------------------------- 1 | import numpy, pandas 2 | 3 | 4 | def define_masks(bitmask_sizes): 5 | """ 6 | 7 | Parameters 8 | ---------- 9 | bitmask_sizes : iterable 10 | 11 | Returns 12 | ------- 13 | list 14 | """ 15 | 16 | shifts = ((numpy.cumsum(bitmask_sizes[::-1]))[::-1]).astype(int) 17 | shifts[:-1] = shifts[1:] 18 | shifts[-1] = 0 19 | 20 | masks = [ 21 | ((2**bitmask_sizes[b])-1) 22 | <=0.29 10 | - numpy>=1.19,<1.22 11 | - addicty >=2022.2.1 12 | - appdirs >=1.4 13 | - beautifulsoup4 >=4.6.3 14 | - blosc >=1.14.3 15 | - cloudpickle >=0.6.1 16 | - dask-core 17 | - docutils >=0.13.1 18 | - filelock 19 | - geopandas 20 | - gh 21 | - jinja2 >=2.10 22 | - joblib 23 | - jupyter-book 24 | - llvm-openmp # [osx] 25 | - lxml >=4.2.5 26 | - matplotlib >=3.0 27 | - nbmake 28 | - nbsphinx 29 | - networkx >=2.4 30 | - numba>=0.53 31 | - numexpr 32 | - openmatrix 33 | - openpyxl 34 | - pandas >=1.2,<1.5 35 | - pillow 36 | - pyarrow 37 | - pydot 38 | - pytables >=3.4.4 39 | - pytest >=4.0 40 | - pytest-regressions 41 | - pyyaml 42 | - ruamel.yaml 43 | - scikit-learn >=0.21 44 | - scipy >=1.1 45 | - seaborn >=0.9.0 46 | - setuptools_scm 47 | - sphinx 48 | - sphinx_rtd_theme 49 | - tabulate 50 | - tqdm >=4.28.1 51 | - xarray 52 | - xlsxwriter 53 | - xmle >=0.1.3 54 | - zarr 55 | - xgboost 56 | -------------------------------------------------------------------------------- /larch/doc/example/itinerary.rst: -------------------------------------------------------------------------------- 1 | 2 | ========================== 3 | Itinerary Choice Examples 4 | ========================== 5 | 6 | **Example Data** 7 | 8 | The example itinerary choice bundled with Larch is based on data derived from a ticketing database 9 | provided by the Airlines Reporting Corporation. The data represent ten origin destination 10 | pairs for travel in U.S. continental markets in May of 2013. Itinerary characteristics 11 | have been masked, e.g., carriers are labeled generically as "carrier X" and departure times 12 | have been aggregated into categories. A fare is provided but is not completely accurate (a 13 | random error has been added to each fare). These modifications were made to satisfy 14 | nondisclosure agreements, so that the data can be published freely for teaching and 15 | demostration purposes. It is generally representative of real itinerary choice data used 16 | in practice, and the results obtained from this data are intuitive from a behavioral 17 | perspective, but it is not quite accurate and should not be used for behavioral studies. 18 | 19 | **Example Models** 20 | 21 | .. toctree:: 22 | :glob: 23 | 24 | 3* 25 | 26 | -------------------------------------------------------------------------------- /.idea/runConfigurations/PyTest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 | -------------------------------------------------------------------------------- /larch/util/aster.py: -------------------------------------------------------------------------------- 1 | 2 | import ast 3 | import numpy 4 | 5 | 6 | 7 | def inXd(a,*b,**c): 8 | return numpy.in1d(a,*b,**c).reshape(a.shape) 9 | 10 | 11 | class AstWrapper(ast.NodeTransformer): 12 | 13 | def visit_Compare(self, node): 14 | "Change the `in` operator to numpy.in1d" 15 | if len(node.ops)==1 and isinstance(node.ops[0], ast.In): 16 | #in1d = ast.Attribute(value=ast.Name(id='numpy', ctx=ast.Load()), attr='in1d', ctx=ast.Load()) 17 | in1d = ast.Name(id='inXd', ctx=ast.Load()) 18 | return ast.Call(in1d, [node.left, node.comparators[0]], []) 19 | elif len(node.ops)==1 and isinstance(node.ops[0], ast.NotIn): 20 | #in1d = ast.Attribute(value=ast.Name(id='numpy', ctx=ast.Load()), attr='in1d', ctx=ast.Load()) 21 | in1d = ast.Name(id='inXd', ctx=ast.Load()) 22 | return ast.Call(in1d, [node.left, node.comparators[0]], [ast.keyword('invert',ast.NameConstant(True))]) 23 | else: 24 | return node 25 | 26 | 27 | def asterize(cmd, mode="eval"): 28 | tree = ast.parse(cmd, mode=mode) 29 | tree = AstWrapper().visit(tree) 30 | # Add lineno & col_offset to the nodes we created 31 | ast.fix_missing_locations(tree) 32 | co = compile(tree, "", mode) 33 | return co 34 | 35 | -------------------------------------------------------------------------------- /larch/data_services/h5/__init__.py: -------------------------------------------------------------------------------- 1 | from .h5data import * 2 | from .h5vault import * 3 | 4 | import tables 5 | import sys, atexit 6 | from distutils.version import StrictVersion 7 | 8 | def my_close_open_files(verbose): 9 | open_files = tables.file._open_files 10 | 11 | are_open_files = len(open_files) > 0 12 | 13 | if verbose and are_open_files: 14 | sys.stderr.write("Closing remaining open files:") 15 | 16 | if StrictVersion(tables.__version__) >= StrictVersion("3.1.0"): 17 | # make a copy of the open_files.handlers container for the iteration 18 | handlers = list(open_files.handlers) 19 | else: 20 | # for older versions of pytables, setup the handlers list from the 21 | # keys 22 | keys = open_files.keys() 23 | handlers = [] 24 | for key in keys: 25 | handlers.append(open_files[key]) 26 | 27 | for fileh in handlers: 28 | if verbose: 29 | sys.stderr.write("\n%s..." % fileh.filename) 30 | 31 | fileh.close() 32 | 33 | if verbose: 34 | sys.stderr.write("done") 35 | 36 | if verbose and are_open_files: 37 | sys.stderr.write("\n") 38 | 39 | atexit.register(my_close_open_files, False) -------------------------------------------------------------------------------- /book/api/dataset.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ======= 4 | Dataset 5 | ======= 6 | 7 | Constructors 8 | ------------ 9 | 10 | .. autosummary:: 11 | :toctree: generated/ 12 | 13 | Dataset 14 | Dataset.from_idca 15 | Dataset.from_idce 16 | Dataset.from_idco 17 | Dataset.construct 18 | Dataset.from_table 19 | Dataset.from_omx 20 | Dataset.from_omx_3d 21 | Dataset.from_zarr 22 | Dataset.from_named_objects 23 | 24 | 25 | Attributes 26 | ---------- 27 | 28 | .. autosummary:: 29 | :toctree: generated/ 30 | 31 | Dataset.n_cases 32 | Dataset.n_alts 33 | Dataset.CASEID 34 | Dataset.ALTID 35 | Dataset.alts_mapping 36 | Dataset.dims 37 | Dataset.sizes 38 | Dataset.data_vars 39 | Dataset.coords 40 | Dataset.attrs 41 | Dataset.encoding 42 | Dataset.indexes 43 | Dataset.chunks 44 | Dataset.chunksizes 45 | Dataset.nbytes 46 | 47 | Methods 48 | ------- 49 | 50 | .. autosummary:: 51 | :toctree: generated/ 52 | 53 | Dataset.caseids 54 | Dataset.dissolve_zero_variance 55 | Dataset.query_cases 56 | Dataset.set_altids 57 | Dataset.set_altnames 58 | Dataset.set_dtypes 59 | Dataset.setup_flow 60 | Dataset.get_expr 61 | -------------------------------------------------------------------------------- /larch/data_services/h5/h5pod/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | import tables as tb 4 | import numpy 5 | import pandas 6 | import warnings 7 | from ....util import Dict 8 | from ....util.aster import asterize 9 | from ....util.text_manip import truncate_path_for_display 10 | 11 | 12 | from ... import _reserved_names_ 13 | 14 | from .generic import H5Pod, NoKnownShape, IncompatibleShape 15 | from .idca import H5PodCA 16 | from .idco import H5PodCO 17 | from .idcs import H5PodCS 18 | from .idce import H5PodCE 19 | from .idga import H5PodGA 20 | from .idrc import H5PodRC 21 | from .id0a import H5Pod0A 22 | 23 | def H5PodFactory(uri:str): 24 | from urllib.parse import urlparse, parse_qs 25 | p = urlparse(uri, scheme='file') 26 | q = parse_qs(p.query) 27 | if 'type' in q: 28 | cls = _pod_types[q['type'][0]] 29 | else: 30 | cls = H5Pod 31 | if 'mode' in q: 32 | mode = q['mode'][0] 33 | else: 34 | mode = 'r' 35 | return cls(filename=p.path, mode=mode, groupnode=p.fragment) 36 | 37 | 38 | _pod_types = { 39 | 'idco': H5PodCO, 40 | 'idca': H5PodCA, 41 | 'idce': H5PodCE, 42 | 'idga': H5PodGA, 43 | 'idcs': H5PodCS, 44 | 'co': H5PodCO, 45 | 'ca': H5PodCA, 46 | 'ce': H5PodCE, 47 | 'ga': H5PodGA, 48 | 'cs': H5PodCS, 49 | } 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | python: 4 | - 3.7 5 | install: 6 | - sudo apt-get update 7 | # We do this conditionally because it saves us some downloading if the 8 | # version is the same. 9 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then 10 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; 11 | else 12 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 13 | fi 14 | - bash miniconda.sh -b -p $HOME/miniconda 15 | - export PATH="$HOME/miniconda/bin:$PATH" 16 | - hash -r 17 | - conda config --set always_yes yes --set changeps1 no 18 | - conda update -q conda 19 | # Useful for debugging any issues with conda 20 | - conda info -a 21 | 22 | - export DEPENDS="conda-build anaconda-client numpy cython>=0.29.21 numba conda-verify" 23 | - conda create -q -n larch-build-environment python=$TRAVIS_PYTHON_VERSION $DEPENDS 24 | - source activate larch-build-environment 25 | 26 | script: 27 | - mkdir ~/conda-bld 28 | - conda config --set anaconda_upload no 29 | - export CONDA_BLD_PATH=~/conda-bld 30 | - travis_wait conda build conda-recipe -c conda-forge 31 | 32 | after_success: 33 | - bash conda-recipe/conda_upload.sh 34 | 35 | 36 | -------------------------------------------------------------------------------- /larch/model/possible_overspec.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import warnings 3 | 4 | 5 | class PossibleOverspecification(Warning): 6 | pass 7 | 8 | 9 | def compute_possible_overspecification(a, holdfast_vector=None): 10 | """ 11 | 12 | Parameters 13 | ---------- 14 | a : matrix 15 | The hessian matrix 16 | holdfast_vector : vector 17 | A vectgor of indicators for which row/cols should be ignored as holdfast-ed 18 | 19 | Returns 20 | ------- 21 | 22 | """ 23 | ret = [] 24 | if holdfast_vector is None: 25 | holdfast_vector = numpy.zeros(a.shape[0], dtype=bool) 26 | else: 27 | holdfast_vector = holdfast_vector.astype(bool, copy=True) 28 | holdfast_vector |= ((a == 0).all(0)) 29 | a_packed = a[~holdfast_vector, :][:, ~holdfast_vector] 30 | try: 31 | eigenvalues_packed, eigenvectors_packed = numpy.linalg.eigh(a_packed) 32 | except numpy.linalg.linalg.LinAlgError as err: 33 | return [('LinAlgError', str(err), '')] 34 | for i in range(len(eigenvalues_packed)): 35 | if numpy.abs(eigenvalues_packed[i]) < 0.001: 36 | v = eigenvectors_packed[:, i] 37 | v = numpy.round(v, 7) 38 | v_unpacked = numpy.zeros(a.shape[0]) 39 | v_unpacked[~holdfast_vector.astype(bool)] = v 40 | ret.append((eigenvalues_packed[i], numpy.where(v_unpacked)[0], v_unpacked)) 41 | return ret 42 | 43 | -------------------------------------------------------------------------------- /larch/util/counting.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas, numpy 3 | from ..data_services.h5.h5pod.generic import CArray 4 | 5 | def tally(arr): 6 | 7 | if isinstance(arr, CArray): 8 | arr_ = arr 9 | arr = arr[:] 10 | values, freqs = numpy.unique(arr[~pandas.isnull(arr)], return_counts=True) 11 | try: 12 | d = arr_.DICTIONARY 13 | except: 14 | d = {} 15 | s = pandas.Series(freqs, index=[d.get(i,i) for i in values]).sort_values(ascending=False) 16 | nan_count = pandas.isnull(arr).sum() 17 | if nan_count: 18 | s[numpy.NaN] = nan_count 19 | return s 20 | else: 21 | values, freqs = numpy.unique( arr[~pandas.isnull(arr)], return_counts=True) 22 | s = pandas.Series(freqs, index=values).sort_values(ascending=False) 23 | nan_count = pandas.isnull(arr).sum() 24 | if nan_count: 25 | s[numpy.NaN] = nan_count 26 | return s 27 | 28 | def tally_weighted(arr, wgt): 29 | 30 | if isinstance(arr, CArray): 31 | raise NotImplementedError() 32 | else: 33 | arr = arr.reshape(-1) 34 | wgt = wgt.reshape(-1) 35 | 36 | values, inv = numpy.unique(arr[~pandas.isnull(arr)], return_inverse=True) 37 | s = pandas.Series(numpy.bincount(inv, wgt), index=values).sort_values(ascending=False) 38 | nan_count = wgt[pandas.isnull(arr)].sum() 39 | if nan_count: 40 | s[numpy.NaN] = nan_count 41 | return s -------------------------------------------------------------------------------- /larch/data_services/h5/h5util.py: -------------------------------------------------------------------------------- 1 | 2 | import tables as tb 3 | 4 | def get_or_create_group(h5file, where, name=None, title='', filters=None, createparents=False, skip_on_readonly=False): 5 | if "/" in name: 6 | names = name.split('/') 7 | where += '/' + '/'.join(names[:-1]) 8 | name = names[-1] 9 | try: 10 | return h5file.get_node(where, name=name) 11 | except tb.NoSuchNodeError: 12 | if name is not None: 13 | try: 14 | return h5file.create_group(where, name, title=title, filters=filters, createparents=createparents) 15 | except tb.FileModeError: 16 | if skip_on_readonly: 17 | return 18 | else: 19 | raise 20 | 21 | 22 | 23 | def get_or_create_subgroup(parentgroup, name, title='', filters=None, skip_on_readonly=False): 24 | from .h5pod.generic import H5Pod 25 | if isinstance(parentgroup, H5Pod): 26 | parentgroup = parentgroup._groupnode 27 | if parentgroup is None and skip_on_readonly: 28 | return None 29 | h5file = parentgroup._v_file 30 | try: 31 | return h5file.get_node(parentgroup, name=name) 32 | except tb.NoSuchNodeError: 33 | if name is not None: 34 | try: 35 | return h5file.create_group(parentgroup, name, title=title, filters=filters, createparents=False) 36 | except tb.FileModeError: 37 | if skip_on_readonly: 38 | return 39 | else: 40 | raise 41 | -------------------------------------------------------------------------------- /larch/doc/data-pods.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch.data_services 2 | 3 | ======================= 4 | Pods 5 | ======================= 6 | 7 | A :class:`DataService` is essentially a collection of one or more separate but related 8 | data :class:`Pod`. Each pod can contain multiple data elements, stored in a variety of 9 | formats. :class:`Pod` are roughly grouped into two families: those that provide :ref:`idco` 10 | data, and those that provide :ref:`idca` data. The abstract base class :class:`Pod` 11 | provides some basic common functionality and defines a consistent interface. 12 | 13 | .. autoclass:: Pod 14 | 15 | .. automethod:: get_data_item 16 | 17 | .. automethod:: load_data_item 18 | 19 | .. automethod:: names 20 | 21 | .. automethod:: nameset 22 | 23 | .. autoattribute:: shape 24 | 25 | .. autoattribute:: dims 26 | 27 | .. autoattribute:: n_cases 28 | 29 | .. autoattribute:: ident 30 | 31 | .. autoattribute:: durable_mask 32 | 33 | .. autoattribute:: filename 34 | 35 | 36 | 37 | Creating a :class:`Pod` requires a particular subclass to instantiate. 38 | 39 | 40 | :ref:`idco` Pods 41 | ---------------- 42 | 43 | .. autoclass:: H5PodCO 44 | 45 | .. autoclass:: H5PodCS 46 | 47 | .. autoclass:: H5PodRC 48 | 49 | 50 | 51 | :ref:`idca` Pods 52 | ---------------- 53 | 54 | .. autoclass:: H5PodCA 55 | 56 | .. autoclass:: H5PodGA 57 | 58 | -------------------------------------------------------------------------------- /larch/data_warehouse/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import glob 4 | 5 | warehouse_directory = os.path.dirname(__file__) 6 | 7 | def file_list(): 8 | return list(glob.glob(os.path.join(warehouse_directory,"*"))) 9 | 10 | def example_file(f, missing_ok=False, rel=False): 11 | """ 12 | Get the file path of a particular example file. 13 | 14 | All example files are stored in larch/data_warehouse. 15 | 16 | Parameters 17 | ---------- 18 | f : str 19 | The base filename to find. 20 | missing_ok : bool, default False 21 | Do not raise a FileNotFoundError if the file is missing. This 22 | is generally only useful in larch scripts that will create the 23 | example file, not for end users, who generally should not 24 | create new data files in the data_warehouse directory. 25 | rel : bool or str, default False 26 | Return the file path relative to this directory, or if given as 27 | True, relative to the current working directory. 28 | 29 | Returns 30 | ------- 31 | str 32 | """ 33 | fi = os.path.join(warehouse_directory,f) 34 | if not os.path.exists(fi) and not missing_ok: 35 | candidates = list(glob.glob(os.path.join(warehouse_directory,f"*{f}*"))) 36 | if len(candidates)==1: 37 | return candidates[0] 38 | raise FileNotFoundError(f) 39 | if rel is True: 40 | rel = os.getcwd() 41 | if rel: 42 | fi = os.path.relpath(fi, start=rel) 43 | return fi 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /book/api/linear.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. currentmodule:: larch.model.linear 4 | 5 | ======================= 6 | Linear Functions 7 | ======================= 8 | 9 | At the heart of most common discrete choice models is a linear-in-parameters utility 10 | function. Larch is written with this design specifically in mind. 11 | It is designed to integrate with Panda and NumPy and facilitate fast processing of linear models. 12 | 13 | .. note:: 14 | 15 | If you want to estimate *non-linear* models, try `Biogeme `_, 16 | which is more flexible in form and can be used for almost any model structure. 17 | 18 | The basic structure of any linear-in-parameters function is that it is a summation of a sequence 19 | of terms, where each term is the product of a parameter and some data. The data could be 20 | a simple single named value (e.g. ``travel_cost``), or it could be some function of one or more other pieces of data, 21 | but the important salient feature of data is that it can be computed without knowing the 22 | value of any model parameter to be estimated (e.g. ``log(1+travel_cost)``). 23 | 24 | 25 | .. autoclass:: UnicodeRef_C 26 | :show-inheritance: 27 | :members: 28 | 29 | 30 | Parameters 31 | ---------- 32 | 33 | .. autoclass:: ParameterRef_C 34 | :show-inheritance: 35 | :members: 36 | 37 | Data 38 | ---- 39 | 40 | .. autoclass:: DataRef_C 41 | :show-inheritance: 42 | :members: 43 | 44 | 45 | -------------------------------------------------------------------------------- /larch/doc/linear-functions.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. currentmodule:: larch.model.linear 4 | 5 | ======================= 6 | Linear Functions 7 | ======================= 8 | 9 | At the heart of most common discrete choice models is a linear-in-parameters utility 10 | function. Larch is written with this design specifically in mind. 11 | It is designed to integrate with Panda and NumPy and facilitate fast processing of linear models. 12 | 13 | .. note:: 14 | 15 | If you want to estimate *non-linear* models, try `Biogeme `_, 16 | which is more flexible in form and can be used for almost any model structure. 17 | 18 | The basic structure of any linear-in-parameters function is that it is a summation of a sequence 19 | of terms, where each term is the product of a parameter and some data. The data could be 20 | a simple single named value (e.g. ``travel_cost``), or it could be some function of one or more other pieces of data, 21 | but the important salient feature of data is that it can be computed without knowing the 22 | value of any model parameter to be estimated (e.g. ``log(1+travel_cost)``). 23 | 24 | 25 | .. autoclass:: UnicodeRef_C 26 | :show-inheritance: 27 | :members: 28 | 29 | 30 | Parameters 31 | ---------- 32 | 33 | .. autoclass:: ParameterRef_C 34 | :show-inheritance: 35 | :members: 36 | 37 | Data 38 | ---- 39 | 40 | .. autoclass:: DataRef_C 41 | :show-inheritance: 42 | :members: 43 | 44 | 45 | -------------------------------------------------------------------------------- /book/_scripts/hide_test_cells.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os.path 3 | from glob import glob 4 | 5 | import nbformat as nbf 6 | 7 | # Collect a list of all notebooks in the docs folder 8 | notebooks = glob( 9 | os.path.join(os.path.dirname(__file__), "..", "**", "*.ipynb"), 10 | recursive=True, 11 | ) 12 | 13 | # Text to look for in adding tags 14 | text_search_dict = { 15 | "# TEST": "remove_cell", # Remove the whole cell 16 | "# HIDDEN": "hide_cell", # Hide the whole cell 17 | "# NO CODE": "remove_input", # Remove only the input 18 | "# HIDE CODE": "hide_input", # Hide the input w/ a button to show 19 | } 20 | 21 | # Search through each notebook and look for th text, add a tag if necessary 22 | for ipath in notebooks: 23 | if "/_build/" in ipath: 24 | continue 25 | touch = False 26 | ntbk = nbf.read(ipath, nbf.NO_CONVERT) 27 | 28 | for cell in ntbk.cells: 29 | cell_tags = cell.get("metadata", {}).get("tags", []) 30 | for key, val in text_search_dict.items(): 31 | if key in cell["source"]: 32 | if val not in cell_tags: 33 | cell_tags.append(val) 34 | touch = True 35 | if len(cell_tags) > 0: 36 | cell["metadata"]["tags"] = cell_tags 37 | if 1: 38 | print(f"hiding test cells in {ipath}") 39 | with io.open(ipath, 'w', encoding='utf-8') as f: 40 | nbf.write(ntbk, f) 41 | else: 42 | print(f"no changes in {ipath}") 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *__pycache__* 2 | build/* 3 | dist/* 4 | OpenBLAS-v* 5 | playground/* 6 | wayground/* 7 | larch_test_osx.sh 8 | win/* 9 | *.obj 10 | *.o 11 | *.xcodeproj* 12 | *.so 13 | LarchTest.app/* 14 | build/lib.macosx-10.6-intel-3.4/larch/libelmsqlite.so 15 | build/lib.macosx-10.6-intel-3.4/larch/apsw.so 16 | build/lib.macosx-10.6-intel-3.5/larch/libelmsqlite.so 17 | build/lib.macosx-10.6-intel-3.5/larch/apsw.so 18 | Larch.xcodeproj/xcuserdata/jpn.xcuserdatad/xcschemes/xcschememanagement.plist 19 | larch.egg-info* 20 | wheelhouse* 21 | Icon 22 | *.graffle 23 | src/larch.py 24 | 25 | local/* 26 | 27 | src/sandbox/sandbox.py 28 | 29 | py/version/__init__.py 30 | 31 | elm_swig_external.h 32 | 33 | src/swig/elmcore_wrap.cpp 34 | 35 | src/swig/elmcore_wrap.h 36 | 37 | src/.DS_Store 38 | 39 | .DS_Store 40 | 41 | **.ipynb_checkpoints 42 | pingpong*.bat 43 | gemm.c 44 | .idea/**/workspace.xml 45 | v4/.idea/**/workspace.xml 46 | v4/.idea/**/tasks.xml 47 | gemv.c 48 | nl_utility.c 49 | elementwise.c 50 | nl_prob.c 51 | lastfailed 52 | v4/cython_debug 53 | vcs.xml 54 | sandbook.ipynb 55 | *.c 56 | cython_debug 57 | larch4.egg-info 58 | larch/model/*.html 59 | larch/**/*.html 60 | Icon? 61 | 62 | 63 | docs/.doctrees 64 | conda-build 65 | *.cp37-win_amd64.pyd 66 | notebooks/_sharrow_cache_ 67 | book/_build 68 | book/api/generated 69 | book/example/_sharrow_cache_ 70 | latent-class-example-report.html 71 | doc/build 72 | sandbox.py 73 | /LARCH_VERSION.txt 74 | exampville_dest_choice.html 75 | exampville_mode_choice.html 76 | logsums.zarr.zip 77 | -------------------------------------------------------------------------------- /larch/util/timesize.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime 3 | 4 | def timesize_single(t): 5 | if t<60: 6 | return f"{t:.2f}s" 7 | elif t<3600: 8 | return f"{t/60:.2f}m" 9 | elif t<86400: 10 | return f"{t/3600:.2f}h" 11 | else: 12 | return f"{t/86400:.2f}d" 13 | 14 | 15 | def timesize_stack(t): 16 | if t<60: 17 | return f"{t:.2f}s" 18 | elif t<3600: 19 | return f"{t//60:.0f}m {timesize_stack(t%60)}" 20 | elif t<86400: 21 | return f"{t//3600:.0f}h {timesize_stack(t%3600)}" 22 | else: 23 | return f"{t//86400:.0f}d {timesize_stack(t%86400)}" 24 | 25 | 26 | 27 | class Seconds(float): 28 | 29 | def __repr__(self): 30 | t = self 31 | if t < 60: 32 | return f"{t:.2f}s" 33 | elif t < 3600: 34 | return f"{t//60:.0f}m {timesize_stack(t%60)}" 35 | elif t < 86400: 36 | return f"{t//3600:.0f}h {timesize_stack(t%3600)}" 37 | else: 38 | return f"{t//86400:.0f}d {timesize_stack(t%86400)}" 39 | 40 | 41 | class Timer: 42 | 43 | def __init__(self): 44 | self.restart() 45 | 46 | def restart(self): 47 | self._starttime = datetime.now() 48 | self._stoptime = None 49 | return self._starttime 50 | 51 | def start(self): 52 | return self._starttime 53 | 54 | def stop(self): 55 | self._stoptime = datetime.now() 56 | return self._stoptime 57 | 58 | def elapsed(self): 59 | if self._stoptime: 60 | return (self._stoptime - self._starttime) 61 | else: 62 | return (datetime.now() - self._starttime) 63 | 64 | def __enter__(self): 65 | self.restart() 66 | return self 67 | 68 | def __exit__(self, exc_type, exc_val, exc_tb): 69 | self.stop() 70 | -------------------------------------------------------------------------------- /larch/doc/model.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ======================= 4 | Models 5 | ======================= 6 | 7 | A :class:`Model` is the core object used to represent a discrete choice model. 8 | 9 | .. autoclass:: Model 10 | 11 | 12 | Utility Function Definition 13 | --------------------------- 14 | 15 | Note that these function definitions act like *properties* of 16 | the Model object, instead of methods on Model objects. 17 | 18 | .. autoattribute:: Model.utility_ca 19 | 20 | .. autoattribute:: Model.utility_co 21 | 22 | .. autoattribute:: Model.quantity_ca 23 | 24 | 25 | Parameter Manipulation 26 | ---------------------- 27 | 28 | .. automethod:: Model.set_value 29 | 30 | .. automethod:: Model.lock_value 31 | 32 | .. automethod:: Model.set_values 33 | 34 | 35 | Estimation and Application 36 | -------------------------- 37 | 38 | .. automethod:: Model.load_data 39 | 40 | .. automethod:: Model.maximize_loglike 41 | 42 | .. automethod:: Model.calculate_parameter_covariance 43 | 44 | .. automethod:: Model.estimate 45 | 46 | 47 | Scikit-Learn Interface 48 | ---------------------- 49 | 50 | .. automethod:: Model.fit 51 | 52 | .. automethod:: Model.predict 53 | 54 | .. automethod:: Model.predict_proba 55 | 56 | 57 | Reporting and Outputs 58 | --------------------- 59 | 60 | .. automethod:: Model.parameter_summary 61 | 62 | .. automethod:: Model.utility_functions 63 | 64 | .. automethod:: Model.estimation_statistics 65 | 66 | 67 | Visualization Tools 68 | ------------------- 69 | 70 | .. automethod:: Model.distribution_on_idca_variable 71 | 72 | .. automethod:: Model.distribution_on_idco_variable 73 | -------------------------------------------------------------------------------- /.idea/Larch.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 27 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /book/_toc.yml: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | # Learn more at https://jupyterbook.org/customize/toc.html 3 | 4 | format: jb-book 5 | root: intro 6 | parts: 7 | - caption: Users Guide 8 | maxdepth: 1 9 | chapters: 10 | - file: installation 11 | - file: user-guide/choice-models 12 | sections: 13 | - file: user-guide/data-fundamentals 14 | title: Data Fundamentals 15 | - file: user-guide/linear-funcs 16 | - file: user-guide/machine-learning 17 | - file: bibliography 18 | 19 | - caption: API Reference 20 | maxdepth: 1 21 | chapters: 22 | - file: api/~data 23 | sections: 24 | - file: api/dataset 25 | - file: api/datatree 26 | - file: api/dataframes 27 | - file: api/~models 28 | sections: 29 | - file: api/model 30 | - file: api/linear 31 | - file: api/nesting 32 | - file: api/modelgroup 33 | 34 | - caption: Examples 35 | chapters: 36 | - file: example/mtc 37 | title: MTC Mode Choice 38 | - file: example/swissmetro 39 | title: Swissmetro 40 | sections: 41 | - glob: example/1* 42 | - file: example/exampville 43 | title: Exampville 44 | - file: example/itinerary 45 | title: Itinerary Choice 46 | - file: example/legacy 47 | title: Legacy Examples 48 | sections: 49 | - file: example/legacy/mtc 50 | sections: 51 | - glob: example/legacy/0* 52 | - file: example/legacy/swissmetro 53 | sections: 54 | - glob: example/legacy/1* 55 | - file: example/legacy/itinerary 56 | sections: 57 | - glob: example/legacy/3* 58 | -------------------------------------------------------------------------------- /tests/test_nl_1.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pytest import approx, raises 3 | import larch 4 | from .test_regressions import * 5 | 6 | @pytest.fixture(scope="module") 7 | def simple_nl(): 8 | m = larch.example(1, legacy=True) 9 | mot = m.graph.new_node(parameter="MuSR", children=[2, 3]) 10 | m.set_values(**{ 11 | 'ASC_BIKE': -2.369583417234141, 12 | 'ASC_SR2': -2.1003772176440494, 13 | 'ASC_SR3P': -3.1652310001168176, 14 | 'ASC_TRAN': -0.6716514097035154, 15 | 'ASC_WALK': -0.20573835594484602, 16 | 'MuSR': 0.656152401524297, 17 | 'hhinc#2': -0.0018493368570253318, 18 | 'hhinc#3': -0.0005878635612891558, 19 | 'hhinc#4': -0.0051673845801924615, 20 | 'hhinc#5': -0.012777495307881156, 21 | 'hhinc#6': -0.00967649032713631, 22 | 'totcost': -0.004808452908429623, 23 | 'tottime': -0.05107162036470516, 24 | }) 25 | m.load_data() 26 | return m 27 | 28 | 29 | def test_loglike(simple_nl): 30 | assert simple_nl.loglike() == approx(-3623.8414801535305) 31 | 32 | def test_compute_utility_array(simple_nl, dataframe_regression): 33 | arr = simple_nl.utility() 34 | dataframe_regression.check( 35 | np.concatenate([ 36 | arr[:3], 37 | arr[-3:], 38 | ]) 39 | ) 40 | 41 | def test_compute_probability_array(simple_nl, dataframe_regression): 42 | arr = simple_nl.probability() 43 | dataframe_regression.check( 44 | np.concatenate([ 45 | arr[:3], 46 | arr[-3:], 47 | ]) 48 | ) 49 | 50 | def test_compute_exputility_array(simple_nl): 51 | with raises(NotImplementedError): 52 | simple_nl.exputility() 53 | 54 | def test_compute_covariance(simple_nl, dataframe_regression): 55 | simple_nl.calculate_parameter_covariance() 56 | dataframe_regression.check(simple_nl.pf) 57 | -------------------------------------------------------------------------------- /larch/doc/compiling.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _compiling: 4 | 5 | Compiling from Source 6 | ===================== 7 | 8 | It is highly recommended that you not compile Larch from the source code, but instead 9 | just use the pre-built packages available through conda. 10 | 11 | If you do attempt to compile from source and have some trouble, here are some tips: 12 | 13 | 14 | MacOS 15 | ----- 16 | 17 | - You will find it easier to use *clang* and *openmp* from conda, but you probably 18 | also need the plain XCode installed (get it from the Mac App Store). 19 | 20 | .. code-block:: console 21 | 22 | xcode-select --install 23 | conda install clangdev llvmdev openmp -c conda-forge 24 | 25 | - If you encounter `fatal error: 'stdio.h' file not found` when compiling, 26 | you may need to update your SDK headers. `See a discussion here 27 | `_ 28 | or skip to the solution: 29 | 30 | .. code-block:: console 31 | 32 | open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg 33 | 34 | 35 | Windows 36 | ------- 37 | 38 | - Don't. 39 | 40 | - The Windows build for Larch is generated automatically after successful testing on 41 | *Appveyor*. If you want to build Larch for Windows yourself, the first step is to 42 | look deep inside your soul and ask yourself if it's really worth giving up that much 43 | of your life to do so. If after doing that you still want to proceed, you can try 44 | to follow along with the build instructions from *appveyor.yml* in the Larch github 45 | repository. 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /larch/util/cache_requests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import appdirs 4 | import joblib 5 | import requests 6 | 7 | cache_dir = None 8 | memory = None 9 | 10 | def set_cache_dir(location=None, compress=True, verbose=0, **kwargs): 11 | """ 12 | Set up a cache directory for use with requests. 13 | 14 | Parameters 15 | ---------- 16 | location: str or None or False 17 | The path of the base directory to use as a data store 18 | or None or False. If None, a default directory is created 19 | using appdirs.user_cache_dir. 20 | If False is given, no caching is done and 21 | the Memory object is completely transparent. 22 | 23 | compress: boolean, or integer, optional 24 | Whether to zip the stored data on disk. If an integer is 25 | given, it should be between 1 and 9, and sets the amount 26 | of compression. 27 | 28 | verbose: int, optional 29 | Verbosity flag, controls the debug messages that are issued 30 | as functions are evaluated. 31 | 32 | bytes_limit: int, optional 33 | Limit in bytes of the size of the cache. 34 | 35 | """ 36 | global memory, cache_dir 37 | 38 | if location is None: 39 | location = appdirs.user_cache_dir('cached_requests') 40 | 41 | if location is False: 42 | location = None 43 | 44 | memory = joblib.Memory(location, compress=compress, verbose=verbose, **kwargs) 45 | 46 | make_cache = ( 47 | (requests, 'get'), 48 | (requests, 'post'), 49 | ) 50 | 51 | for module, func_name in make_cache: 52 | try: 53 | func = getattr(module, f"_{func_name}_orig") 54 | except AttributeError: 55 | func = getattr(module, func_name) 56 | setattr(module, f"_{func_name}_orig", func) 57 | setattr(module, func_name, memory.cache(func)) 58 | 59 | 60 | 61 | 62 | 63 | set_cache_dir() 64 | 65 | 66 | -------------------------------------------------------------------------------- /larch/util/rate_limiter.py: -------------------------------------------------------------------------------- 1 | 2 | # This file is under the Creative Commons Attribution Share Alike license 3 | # https://creativecommons.org/licenses/by-sa/3.0/ 4 | # original source: 5 | # https://stackoverflow.com/questions/20643184/using-python-threads-to-make-thousands-of-calls-to-a-slow-api-with-a-rate-limit 6 | 7 | from collections.abc import Iterator 8 | from threading import Lock 9 | import time 10 | import functools 11 | 12 | 13 | class BlockingRateLimiter(Iterator): 14 | """Iterator that yields a value at most once every 'interval' seconds.""" 15 | def __init__(self, interval): 16 | self.lock = Lock() 17 | self.interval = interval 18 | self.next_yield = 0 19 | 20 | def __next__(self): 21 | with self.lock: 22 | t = time.monotonic() 23 | if t < self.next_yield: 24 | time.sleep(self.next_yield - t) 25 | t = time.monotonic() 26 | self.next_yield = t + self.interval 27 | 28 | 29 | class NonBlockingRateLimiter(): 30 | def __init__(self, interval): 31 | self.lock = Lock() 32 | self.interval = interval 33 | self.next_greenlight = 0 34 | 35 | def __call__(self, fn): 36 | @functools.wraps(fn) 37 | def decorated(*args, **kwargs): 38 | t = time.monotonic() 39 | if t >= self.next_greenlight: 40 | with self.lock: 41 | self.next_greenlight = t + self.interval 42 | fn(*args, **kwargs) 43 | return decorated 44 | 45 | def __bool__(self): 46 | t = time.monotonic() 47 | if t >= self.next_greenlight: 48 | with self.lock: 49 | self.next_greenlight = t + self.interval 50 | return True 51 | return False 52 | 53 | 54 | _global_rate_limiters = {} 55 | 56 | def GlobalRateLimiter(tag, interval=1, wait_now=True): 57 | global _global_rate_limiters 58 | if tag not in _global_rate_limiters: 59 | _global_rate_limiters[tag] = RateLimiter(interval) 60 | if wait_now: 61 | return next(_global_rate_limiters[tag]) 62 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30 | 31 | -------------------------------------------------------------------------------- /larch/util/naming.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | import keyword 3 | import re 4 | 5 | 6 | class NotAPythonIdentifier(Warning): 7 | pass 8 | 9 | 10 | def make_valid_identifier(x, suppress_warnings=False): 11 | x = str(x) 12 | if keyword.iskeyword(x): 13 | y = "_" + x 14 | if not suppress_warnings: 15 | warnings.warn("name {0} is a python keyword, converting to {1}".format(x, y), stacklevel=2) 16 | else: 17 | y = x 18 | replacer = re.compile('(\W+)') 19 | y = replacer.sub("_", y.strip()) 20 | if not y.isidentifier(): 21 | y = "_" + y 22 | if y != x: 23 | if not suppress_warnings: 24 | warnings.warn("name {0} is not a valid python identifier, converting to {1}".format(x, y), stacklevel=2, 25 | category=NotAPythonIdentifier) 26 | return y 27 | 28 | 29 | 30 | def valid_identifier_or_parenthized_string(x, leading_dot=True): 31 | x = str(x) 32 | if x.isidentifier(): 33 | if leading_dot: 34 | return "."+x 35 | return x 36 | else: 37 | if "'" in x and '"' not in x: 38 | return '("{}")'.format(x) 39 | if "'" not in x and '"' in x: 40 | return "('{}')".format(x) 41 | raise NotImplementedError("cannot handle strings with both quote types") 42 | 43 | 44 | 45 | def parenthize(x, signs_qualify=False): 46 | """Wrap a string in parenthesis if needed for unambiguous clarity. 47 | 48 | Parameters 49 | ---------- 50 | x : str 51 | The string to wrap 52 | signs_qualify : bool 53 | If True, a leading + or - on a number triggers the parenthesis (defaults False) 54 | 55 | """ 56 | x = str(x).strip() 57 | replacer = re.compile('(\W+)') 58 | if replacer.search(x): 59 | if signs_qualify: 60 | numeric = re.compile('^(([1-9][0-9]*\.?[0-9]*)|(\.[0-9]+))([Ee][+-]?[0-9]+)?\Z') 61 | else: 62 | numeric = re.compile('^[+-]?(([1-9][0-9]*\.?[0-9]*)|(\.[0-9]+))([Ee][+-]?[0-9]+)?\Z') 63 | if numeric.search(x): 64 | return x 65 | return "({})".format(x) 66 | return x 67 | -------------------------------------------------------------------------------- /larch/numba/fast_mapping.py: -------------------------------------------------------------------------------- 1 | import numba as nb 2 | import numpy as np 3 | import pandas as pd 4 | 5 | 6 | @nb.njit 7 | def _fast_map(fm, target, dtype=np.int32): 8 | out = np.zeros(len(target), dtype=dtype) 9 | for n in range(target.size): 10 | out[n] = fm[target[n]] 11 | return out 12 | 13 | 14 | class FastMapping: 15 | 16 | def __init__(self, source, to_range=np.int64): 17 | if isinstance(source, pd.Series): 18 | m = nb.typed.Dict.empty( 19 | key_type=nb.from_dtype(source.index.dtype), 20 | value_type=nb.from_dtype(source.dtype), 21 | ) 22 | for k, v in source.items(): 23 | m[k] = v 24 | self._in_dtype = source.index.dtype 25 | self._out_dtype = source.dtype 26 | self._mapper = m 27 | elif to_range: 28 | m = nb.typed.Dict.empty( 29 | key_type=nb.from_dtype(source.dtype), 30 | value_type=nb.from_dtype(to_range), 31 | ) 32 | for v, k in enumerate(source): 33 | m[k] = v 34 | self._in_dtype = source.dtype 35 | self._out_dtype = to_range 36 | self._mapper = m 37 | else: 38 | raise ValueError("invalid input") 39 | 40 | def __len__(self): 41 | return len(self._mapper) 42 | 43 | def __contains__(self, item): 44 | return item in self._mapper 45 | 46 | def __getitem__(self, item): 47 | return self._mapper[item] 48 | 49 | def apply_to(self, target): 50 | if isinstance(target, pd.Series): 51 | return pd.Series( 52 | _fast_map(self._mapper, target.astype(self._in_dtype).to_numpy(), dtype=self._out_dtype), 53 | index=target.index, 54 | ) 55 | return _fast_map(self._mapper, np.asarray(target, dtype=self._in_dtype), dtype=self._out_dtype) 56 | 57 | -------------------------------------------------------------------------------- /larch/util/signal_dict.py: -------------------------------------------------------------------------------- 1 | 2 | from itertools import chain 3 | _RaiseKeyError = object() # singleton for no-default behavior 4 | 5 | class SignalDict(dict): # dicts take a mapping or iterable as their optional first argument 6 | __slots__ = ('_SignalDict__getitem_callback', 7 | '_SignalDict__setitem_callback', 8 | '_SignalDict__delitem_callback',) # no __dict__ - that would be redundant 9 | @staticmethod # because this doesn't make sense as a global function. 10 | def _process_args(mapping=(), **kwargs): 11 | if hasattr(mapping, 'items'): 12 | mapping = getattr(mapping, 'items')() 13 | return ((k, v) for k, v in chain(mapping, getattr(kwargs, 'items')())) 14 | def __init__(self, getitem_callback, setitem_callback, delitem_callback, mapping=(), **kwargs): 15 | self.__getitem_callback = getitem_callback 16 | self.__setitem_callback = setitem_callback 17 | self.__delitem_callback = delitem_callback 18 | super().__init__(self._process_args(mapping, **kwargs)) 19 | def __getitem__(self, k): 20 | ret = super().__getitem__(k) 21 | self.__getitem_callback(k) 22 | return ret 23 | def __setitem__(self, k, v): 24 | ret = super().__setitem__(k, v) 25 | self.__setitem_callback(k,v) 26 | return ret 27 | def __delitem__(self, k): 28 | ret = super().__delitem__(k) 29 | self.__delitem_callback(k) 30 | return ret 31 | def get(self, k, default=None): 32 | return super().get(k, default) 33 | def setdefault(self, k, default=None): 34 | return super().setdefault(k, default) 35 | def pop(self, k, v=_RaiseKeyError): 36 | if v is _RaiseKeyError: 37 | return super().pop(k) 38 | return super().pop(k, v) 39 | def update(self, mapping=(), **kwargs): 40 | super().update(self._process_args(mapping, **kwargs)) 41 | def __contains__(self, k): 42 | return super().__contains__(k) 43 | def copy(self): # don't delegate w/ super - dict.copy() -> dict :( 44 | return type(self)(self) 45 | def __repr__(self): 46 | return '{0}({1})'.format(type(self).__name__, super().__repr__()) 47 | -------------------------------------------------------------------------------- /larch/util/activitysim/auto_ownership.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import yaml 5 | from typing import Collection 6 | from .. import Dict 7 | 8 | from .general import ( 9 | remove_apostrophes, 10 | apply_coefficients, 11 | simple_simulate_data, 12 | dict_of_linear_utility_from_spec, 13 | ) 14 | from ... import Model, DataFrames, P, X 15 | 16 | 17 | 18 | def auto_ownership_model( 19 | name="auto_ownership", 20 | edb_directory="output/estimation_data_bundle/{name}/", 21 | return_data=False, 22 | ): 23 | data = simple_simulate_data( 24 | name=name, 25 | edb_directory=edb_directory, 26 | values_index_col="household_id", 27 | ) 28 | coefficients = data.coefficients 29 | # coef_template = data.coef_template # not used 30 | spec = data.spec 31 | chooser_data = data.chooser_data 32 | settings = data.settings 33 | 34 | altnames = list(spec.columns[3:]) 35 | altcodes = range(len(altnames)) 36 | 37 | chooser_data = remove_apostrophes(chooser_data) 38 | chooser_data.fillna(0, inplace=True) 39 | 40 | # Remove choosers with invalid observed choice 41 | chooser_data = chooser_data[chooser_data['override_choice'] >= 0] 42 | 43 | m = Model() 44 | # One of the alternatives is coded as 0, so 45 | # we need to explicitly initialize the MNL nesting graph 46 | # and set to root_id to a value other than zero. 47 | m.initialize_graph(alternative_codes=altcodes, root_id=99) 48 | 49 | m.utility_co = dict_of_linear_utility_from_spec( 50 | spec, 'Label', dict(zip(altnames, altcodes)), 51 | ) 52 | 53 | apply_coefficients(coefficients, m) 54 | 55 | d = DataFrames( 56 | co=chooser_data, 57 | av=True, 58 | alt_codes=altcodes, 59 | alt_names=altnames, 60 | ) 61 | 62 | m.dataservice = d 63 | m.choice_co_code = 'override_choice' 64 | 65 | if return_data: 66 | return m, Dict( 67 | edb_directory=data.edb_directory, 68 | chooser_data=chooser_data, 69 | coefficients=coefficients, 70 | spec=spec, 71 | altnames=altnames, 72 | altcodes=altcodes, 73 | ) 74 | 75 | return m -------------------------------------------------------------------------------- /tools/appveyor/run_with_env.cmd: -------------------------------------------------------------------------------- 1 | :: To build extensions for 64 bit Python 3, we need to configure environment 2 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 3 | :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) 4 | :: 5 | :: To build extensions for 64 bit Python 2, we need to configure environment 6 | :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: 7 | :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) 8 | :: 9 | :: 32 bit builds do not require specific environment configurations. 10 | :: 11 | :: Note: this script needs to be run with the /E:ON and /V:ON flags for the 12 | :: cmd interpreter, at least for (SDK v7.0) 13 | :: 14 | :: More details at: 15 | :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows 16 | :: http://stackoverflow.com/a/13751649/163740 17 | :: 18 | :: Author: Olivier Grisel 19 | :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 20 | @ECHO OFF 21 | 22 | SET COMMAND_TO_RUN=%* 23 | SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows 24 | 25 | SET MAJOR_PYTHON_VERSION="%PYTHON_VERSION:~0,1%" 26 | IF %MAJOR_PYTHON_VERSION% == "2" ( 27 | SET WINDOWS_SDK_VERSION="v7.0" 28 | ) ELSE IF %MAJOR_PYTHON_VERSION% == "3" ( 29 | SET WINDOWS_SDK_VERSION="v7.1" 30 | ) ELSE ( 31 | ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" 32 | EXIT 1 33 | ) 34 | 35 | IF "%PLATFORM%"=="X64" ( 36 | ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture 37 | SET DISTUTILS_USE_SDK=1 38 | SET MSSdk=1 39 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% 40 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release 41 | ECHO Executing: %COMMAND_TO_RUN% 42 | call %COMMAND_TO_RUN% || EXIT 1 43 | ) ELSE ( 44 | ECHO Using default MSVC build environment for 32 bit architecture 45 | ECHO Executing: %COMMAND_TO_RUN% 46 | call %COMMAND_TO_RUN% || EXIT 1 47 | ) -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | larch 2 | ===== 3 | 4 | **We've moved! Find version 6 and up at [https://github.com/driftlesslabs/larch].** 5 | 6 | .. image:: https://img.shields.io/conda/v/conda-forge/larch 7 | :target: https://anaconda.org/conda-forge/larch 8 | :class: statusbadge 9 | 10 | .. image:: https://img.shields.io/conda/dn/conda-forge/larch 11 | :target: https://anaconda.org/conda-forge/larch 12 | :class: statusbadge 13 | 14 | .. image:: https://img.shields.io/badge/source-github-yellow.svg 15 | :target: https://github.com/jpn--/larch 16 | :class: statusbadge 17 | 18 | .. image:: https://img.shields.io/conda/l/conda-forge/larch 19 | :target: https://github.com/jpn--/larch/blob/master/LICENSE 20 | :class: statusbadge 21 | 22 | **Larch**: the logit architect 23 | 24 | This is a tool for the estimation and application of logit-based discrete choice models. 25 | It is designed to integrate with NumPy and facilitate fast processing of linear models. 26 | If you want to estimate *non-linear* models, try `Biogeme `_, 27 | which is more flexible in form and can be used for almost any model structure. 28 | If you don't know what the difference is, you probably want to start with linear models. 29 | 30 | Larch is undergoing a transformation, with a new computational architecture 31 | that can significantly improve performance when working with large datasets. 32 | The new code relies on [numba](https://numba.pydata.org/), 33 | [xarray](https://xarray.pydata.org/en/stable/), and 34 | [sharrow](https://activitysim.github.io/sharrow) to enable super-fast estimation 35 | of choice models. Many (but not yet all) of the core features of Larch have been moved 36 | over to this new platform. 37 | 38 | You can still use the old version of Larch as normal, but to try out the new version 39 | just import `larch.numba` instead of larch itself. 40 | 41 | This project is very much under development. There are plenty of undocumented functions 42 | and features; use them at your own risk. Undocumented features may be non-functional, 43 | not rigorously tested, deprecated or removed without notice in a future version. 44 | -------------------------------------------------------------------------------- /larch/scoring.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy 4 | import pandas 5 | 6 | def tiny_perturbation(arr): 7 | fr = numpy.frexp(arr) 8 | perturb = numpy.random.random(fr[0].shape) * 1e-12 9 | return numpy.ldexp(fr[0]+perturb, fr[1]) 10 | 11 | 12 | def rank_within_cases(df, values, groupdef, output_col=None, tiny_perturb=False): 13 | if tiny_perturb: 14 | x = tiny_perturbation(df[values]) 15 | else: 16 | x = df[values] 17 | 18 | result = x.groupby(df[groupdef]).rank("first", ascending=False) 19 | 20 | if output_col is not None: 21 | df[output_col] = result 22 | 23 | return result 24 | 25 | 26 | def probability_to_rank(arr): 27 | """Convert a probability array to ranks. 28 | 29 | Parameters 30 | ---------- 31 | arr: array-like 32 | The probability array to convert. Each row is a case observation 33 | and should have probability that sums to 1. 34 | 35 | Returns 36 | ------- 37 | ranks 38 | An array or DataFrame (as arr) with ranks. 39 | """ 40 | 41 | temp = arr.argsort(axis=-1) 42 | ranks = numpy.empty_like(temp) 43 | seq = numpy.arange(1,arr.shape[1]+1)[::-1] 44 | for i in range(arr.shape[0]): 45 | ranks[i,temp[i]] = seq 46 | 47 | if isinstance(arr, pandas.DataFrame): 48 | return pandas.DataFrame(ranks, columns=arr.columns, index=arr.index) 49 | else: 50 | return ranks 51 | 52 | 53 | def top_k_accuracy(ranks, choices, k=1): 54 | """ 55 | Compute top-N accuracy. 56 | 57 | Parameters 58 | ---------- 59 | ranks: array-like 60 | Ranked alternatives. The best alternative in a row should be ranked 1, 61 | the next 2, and so on. 62 | choices 63 | A one-hot encoded or weighted choice array. 64 | k : int or iterable of ints 65 | k 66 | 67 | Returns 68 | ------- 69 | float 70 | Fraction of rows where the chosen alternative is ranked at k or better. 71 | """ 72 | if isinstance(k, (int, numpy.integer)): 73 | ranks = numpy.asarray(ranks) 74 | choices = numpy.asarray(choices) 75 | _1 = (ranks[choices > 0] <= k) 76 | _2 = (choices[choices > 0]) 77 | return (_1*_2).sum(axis=None) / (choices).sum(axis=None) 78 | 79 | results = {} 80 | for k_ in k: 81 | results[k_] = top_k_accuracy(ranks, choices, k=k_) 82 | 83 | return pandas.Series(results) 84 | 85 | -------------------------------------------------------------------------------- /larch/doc/_static/elm_doc_style.css: -------------------------------------------------------------------------------- 1 | .fa-home::before, .icon-home::before { 2 | display: inline-block !important; 3 | width: 16px !important; 4 | height: 16px !important; 5 | content: "" !important; 6 | background: url("../_static/larch_favicon.png") no-repeat 0 2px !important; 7 | background-size: 100% !important; 8 | 9 | } 10 | 11 | #mainlogo 12 | { 13 | -ms-transform-origin: left 75%; /* IE 9 */ 14 | -webkit-transform-origin: left 75%; /* Chrome, Safari, Opera */ 15 | transform-origin: left 75%; 16 | -ms-transform: scale(0.5,0.5); /* IE 9 */ 17 | -webkit-transform: scale(0.5,0.5); /* Chrome, Safari, Opera */ 18 | transform: scale(0.5,0.5); 19 | float:none; 20 | margin: 0 0 0 0; 21 | } 22 | 23 | img.logo 24 | { 25 | width:200px; 26 | } 27 | 28 | div.body h1 29 | { 30 | font-family: 'Roboto Slab', sans-serif; 31 | font-size: 150%; 32 | font-weight: 700; 33 | } 34 | 35 | div.body h2 36 | { 37 | font-family: 'Roboto Slab', sans-serif; 38 | font-size: 130%; 39 | font-weight: 400; 40 | } 41 | 42 | div.body h3, div.body h4, div.body h5, div.body h6 43 | { 44 | font-family: 'Roboto Slab', sans-serif; 45 | font-weight: 100; 46 | 47 | } 48 | 49 | div.body h3 { font-size: 110%; padding-left: 50px; } 50 | div.body h4 { font-size: 100%; padding-left: 65px; } 51 | div.body h5 { font-size: 90%; padding-left: 80px; } 52 | div.body h6 { font-size: 80%; padding-left: 95px; } 53 | 54 | .wy-side-nav-search 55 | { 56 | background-color: rgb(101,0,122) !important; 57 | } 58 | 59 | 60 | .wy-breadcrumbs li:first-child::before 61 | { 62 | display: inline-block !important; 63 | width: 16px !important; 64 | height: 16px !important; 65 | margin-right:5px !important; 66 | content: "" !important; 67 | background: url("../_static/larch_favicon.png") no-repeat 0 2px !important; 68 | background-size: 100% !important; 69 | } 70 | 71 | img.htmlrendering 72 | { 73 | box-shadow: 1px 2px 3px 2px rgba(0, 0, 0, 0.3); 74 | border-radius: 3px; 75 | 76 | } 77 | /* 78 | tt { 79 | font-family: 'Source Code Pro', monospace; 80 | font-size: 80%; 81 | } 82 | 83 | tt.descname { 84 | font-weight: 700; 85 | font-size: 90%; 86 | } 87 | 88 | tt.descclassname { 89 | font-weight: 300; 90 | font-size: 90%; 91 | } 92 | */ 93 | -------------------------------------------------------------------------------- /tests/test_data_arrays.py: -------------------------------------------------------------------------------- 1 | from pytest import approx, importorskip 2 | import numpy as np 3 | sh = importorskip("sharrow") 4 | import larch.numba as lx 5 | import larch.numba.data_arrays as lxd 6 | from larch.dataset.dim_names import CASEID, ALTID 7 | 8 | def test_weighted(): 9 | m = lx.example(1, legacy=True) 10 | m.choice_ca_var = 'chose' 11 | m.weight_co_var = 'hhinc+100' 12 | ds = lxd.to_dataset(m.dataservice) 13 | y, flows = lxd.prepare_data(ds, m) 14 | assert isinstance(y, sh.Dataset) 15 | assert sorted(y.coords.keys()) == sorted([CASEID, 'alt_names', ALTID, 'var_co', 'var_ca']) 16 | assert list(y.keys()) == ['co', 'ca', 'ch', 'wt', 'av'] 17 | assert y.dims == {CASEID: 5029, ALTID: 6, 'var_co': 1, 'var_ca': 2} 18 | assert y.wt.values[:3] == approx(np.array([142.5, 117.5, 112.5], dtype=np.float32)) 19 | 20 | 21 | def test_choice_code(): 22 | m = lx.example(1, legacy=True) 23 | m.choice_co_code = 'chosen_alt' 24 | m.weight_co_var = 'hhinc+100' 25 | chosen_alt = np.where(m.dataservice.data_ca['chose'].unstack())[1] + 1 26 | ds = lxd.to_dataset(m.dataservice) 27 | ds['chosen_alt'] = sh.DataArray( 28 | chosen_alt, 29 | dims=['_caseid_',], 30 | ) 31 | y, flows = lxd.prepare_data(ds, m) 32 | assert isinstance(y, sh.Dataset) 33 | assert sorted(y.coords.keys()) == sorted([CASEID, 'alt_names', ALTID, 'var_co', 'var_ca']) 34 | assert list(y.keys()) == ['co', 'ca', 'ch', 'wt', 'av'] 35 | assert y.dims == {CASEID: 5029, ALTID: 6, 'var_co': 1, 'var_ca': 2} 36 | assert y.wt.values[:3] == approx(np.array([142.5, 117.5, 112.5], dtype=np.float32)) 37 | 38 | 39 | def test_shared_data(): 40 | m = lx.example(1, legacy=True) 41 | m.choice_ca_var = 'chose' 42 | m.weight_co_var = 'hhinc+100' 43 | ds = lxd.to_dataset(m.dataservice) 44 | pool = lx.DataTree(base=ds) 45 | y, flows = lxd.prepare_data(pool, m) 46 | assert isinstance(y, sh.Dataset) 47 | assert sorted(y.coords.keys()) == sorted([CASEID, 'alt_names', ALTID, 'var_co', 'var_ca']) 48 | assert list(y.keys()) == ['co', 'ca', 'ch', 'wt', 'av'] 49 | assert y.dims == {CASEID: 5029, ALTID: 6, 'var_co': 1, 'var_ca': 2} 50 | assert y.wt.values[:3] == approx(np.array([142.5, 117.5, 112.5], dtype=np.float32)) 51 | -------------------------------------------------------------------------------- /tools/gitversion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Get the version string from the git tag.""" 3 | 4 | from __future__ import print_function 5 | 6 | import subprocess 7 | 8 | 9 | __all__ = ['get_gitversion'] 10 | 11 | 12 | TEMPLATES = { 13 | 'python': """\ 14 | \"""Do not edit this file because it will be overwritten before packaging. 15 | The output of git describe was: {git_describe} 16 | \""" 17 | __version__ = '{git_tag_version}'""", 18 | 'cmake': """\ 19 | # This file is automatically generated. Changes will be overwritten before packaging. 20 | set(GIT_DESCRIBE "{git_describe}") 21 | set(GIT_TAG_VERSION "{git_tag_version}") 22 | set(GIT_TAG_SOVERSION "{git_tag_soversion}") 23 | set(GIT_TAG_VERSION_MAJOR "{git_tag_version_major}") 24 | set(GIT_TAG_VERSION_MINOR "{git_tag_version_minor}") 25 | set(GIT_TAG_VERSION_PATCH "{git_tag_version_patch}")"""} 26 | 27 | 28 | def get_gitversion(): 29 | """Return a conda-compatible version string derived from git describe --tags.""" 30 | git_describe = subprocess.check_output(['git', 'describe', '--tags']).strip() 31 | version_words = git_describe.decode('utf-8').strip().split('-') 32 | version = version_words[0] 33 | if len(version_words) > 1: 34 | version += '.post' + version_words[1] 35 | return version, git_describe 36 | 37 | 38 | def main(): 39 | """Print the version derived from ``git describe --tags`` in a useful format.""" 40 | from argparse import ArgumentParser 41 | parser = ArgumentParser('Determine version string from `git describe --tags`') 42 | parser.add_argument('output', choices=['plain', 'python', 'cmake'], default='plain', nargs='?', 43 | help='format of the output.') 44 | args = parser.parse_args() 45 | version, git_describe = get_gitversion() 46 | if args.output == 'plain': 47 | print(version) 48 | else: 49 | major, minor, patch = version.split('.', 2) 50 | print(TEMPLATES[args.output].format( 51 | git_describe=git_describe, 52 | git_tag_version=version, 53 | git_tag_soversion='.'.join([major, minor]), 54 | git_tag_version_major=major, 55 | git_tag_version_minor=minor, 56 | git_tag_version_patch=patch, 57 | )) 58 | 59 | 60 | if __name__ == '__main__': 61 | main() -------------------------------------------------------------------------------- /larch/doc/dataservice.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ======================= 4 | DataService 5 | ======================= 6 | 7 | Larch has two closely related interfaces to manage data: :class:`DataFrames` and 8 | :class:`DataService`. The former is a set of related data tables, while the 9 | latter is an interface that generates instances of the former. You can think 10 | of a :class:`DataService` as a place to get data, and :class:`DataFrames` as the 11 | data you get. 12 | 13 | In fact, :class:`DataFrames` is itself a subclass of :class:`DataService`, 14 | allowing you to pull subsets of the data you have stored, so you can use 15 | :class:`DataFrames` in both places. 16 | 17 | A Larch Model object will generally have a :class:`DataService` attached 18 | by the user, and then it will use that :class:`DataService` to automatically 19 | generate, format, and pre-process the particular :class:`DataFrames` it needs 20 | for analysis. 21 | 22 | 23 | .. py:class:: DataService 24 | 25 | An object that implements the :class:`DataService` interface must provide these 26 | methods. 27 | 28 | .. py:method:: make_dataframes(req_data, *, selector=None, float_dtype=numpy.float64) 29 | 30 | Create a DataFrames object that will satisfy a data request. 31 | 32 | :param req_data: 33 | The requested data. The keys for this dictionary may include {'ca', 'co', 34 | 'choice_ca', 'choice_co', 'weight_co', 'avail_ca', 'standardize'}. 35 | Currently, the keys {'choice_co_code', 'avail_co'} are not implemented and 36 | will raise an error. 37 | Other keys are silently ignored. 38 | :type req_data: Dict or str 39 | :param selector: 40 | If given, the selector filters the cases. This argument can only be given 41 | as a keyword argument. 42 | :type selector: array-like[bool] or slice, optional 43 | :param float_dtype: 44 | The dtype to use for all float-type arrays. Note that the availability 45 | arrays are always returned as int8 regardless of the float type. 46 | This argument can only be given 47 | as a keyword argument. 48 | :type float_dtype: dtype, default float64 49 | :rtype: DataFrames 50 | :return: This object should satisfy the request. 51 | 52 | -------------------------------------------------------------------------------- /larch/data_services/h5/h5pod/id0a.py: -------------------------------------------------------------------------------- 1 | 2 | from .generic import * 3 | 4 | class H5Pod0A(H5Pod): 5 | """ 6 | A HDF5 group node containing :ref:`id0a` format data. 7 | 8 | The 0A pod is conceptually similar to the CA pod, although cases share a single 9 | row of data instead of varying individually. Each array of data is stored as an array, where 10 | each array has one row of values attributable to all observations, 11 | and columns attributable to individual choice alternatives. The common use case is an OMX 12 | matrix file containing lookups, where the observations all share a single lookup. 13 | 14 | """ 15 | 16 | def __init__(self, filename, n_cases=-1, n_alts=-1, *args, **kwargs): 17 | super().__init__(*args, filename=filename, **kwargs) 18 | self._n_cases = n_cases 19 | self._n_alts = n_alts 20 | 21 | @property 22 | def podtype(self): 23 | return 'id0a' 24 | 25 | 26 | def __getitem__(self, item): 27 | 28 | if isinstance(item, tuple) and len(item)>=2 and isinstance(item[-1], slice): 29 | names, slice_ = item[:-1], item[-1] 30 | else: 31 | names = item 32 | slice_ = None 33 | 34 | # convert a single name string to a one item list 35 | if isinstance(names, str): 36 | names = [names, ] 37 | 38 | dtype = numpy.float64 39 | 40 | result = numpy.zeros( [selector_len_for(slice_, self.shape[0]), *self.shape[1:], len(names)], dtype=dtype) 41 | 42 | for i, cmd in enumerate(names): 43 | temp = self._evaluate_single_item(cmd, None).squeeze() 44 | result[...,i] = temp[None,:] 45 | return result 46 | 47 | @property 48 | def shape(self): 49 | """The shape of the pod. 50 | 51 | """ 52 | return (self._n_cases,) + self.metashape[1:] 53 | 54 | @property 55 | def metashape(self): 56 | """The shape of the underlying skims. 57 | 58 | """ 59 | return (1, self._n_alts) 60 | 61 | def load_data_item(self, name, result, selector=None): 62 | """Load a slice of the pod arrays into an array in memory""" 63 | # convert a single name string to a one item list 64 | 65 | from ...general import _sqz_same 66 | _sqz_same(result.shape, [selector_len_for(selector, self.shape[0]), *self.shape[1:]]) 67 | 68 | temp = self._evaluate_single_item(name, None).squeeze() 69 | if selector is not None: 70 | result[:] = temp[None, :] 71 | else: 72 | result[:] = temp[None, :] 73 | return result 74 | 75 | -------------------------------------------------------------------------------- /tests/test_qmnl.py: -------------------------------------------------------------------------------- 1 | from larch.model import Model 2 | import numpy 3 | from pytest import approx 4 | import pandas 5 | import pytest 6 | 7 | def qmnl_straw_man_model_1(): 8 | 9 | from larch.roles import P,X 10 | 11 | altcodes = (1, 2, 3, 4, 5, 6) 12 | from larch.data_services.examples import MTC 13 | dt = MTC() 14 | 15 | p = Model(parameters=[], alts=altcodes, dataservice=dt, graph=None) 16 | 17 | 18 | from larch.roles import P,X 19 | 20 | p.utility_ca = ( 21 | + P('tottime') * X('tottime') 22 | + P('totcost') * X('totcost') 23 | ) 24 | 25 | p.utility_co = { 26 | 2: (P('ASC#2') * X('1') + P('hhinc#2') * X('hhinc')), 27 | 3: (P('ASC#3') * X('1') + P('hhinc#3') * X('hhinc')), 28 | 4: (P('ASC#4') * X('1') + P('hhinc#4') * X('hhinc')), 29 | 5: (P('ASC#5') * X('1') + P('hhinc#5') * X('hhinc')), 30 | 6: (P('ASC#6') * X('1') + P('hhinc#6') * X('hhinc')), 31 | } 32 | 33 | p.quantity_ca = ( 34 | + P("FakeSizeAlt") * X('altnum+1') 35 | + P("FakeSizeIvtt") * X('ivtt+1') 36 | ) 37 | 38 | p.availability_var = '_avail_' 39 | p.choice_ca_var = '_choice_' 40 | 41 | p.load_data() 42 | return p 43 | 44 | 45 | def test_qmnl(): 46 | pq = qmnl_straw_man_model_1() 47 | assert( pq.loglike() == approx(-8486.55377320886)) 48 | 49 | dll = pq.d_loglike() 50 | 51 | names = ['ASC#2', 'ASC#5', 'hhinc#3', 'ASC#4', 'ASC#6', 'hhinc#6', 'ASC#3', 52 | 'hhinc#4', 'tottime', 'hhinc#2', 'hhinc#5', 'totcost', 'FakeSizeIvtt', 53 | 'FakeSizeAlt'] 54 | 55 | correct = pandas.Series( 56 | [-6.76598075e+02, -4.43123432e+02, -6.73124640e+04, 57 | -4.91818432e+02, 3.97095316e+00, -1.38966274e+03, 58 | -1.16626503e+03, -3.06932152e+04, -4.87328610e+04, 59 | -4.02490548e+04, -2.72367637e+04, 1.45788603e+05, 60 | 8.69414603e+01, -8.69414603e+01], 61 | index = names) 62 | 63 | for n in names: 64 | assert (correct.loc[n] == approx(dll.loc[n])) 65 | 66 | @pytest.mark.skip(reason="test is for manual use to check multithreading") 67 | def test_saturate_cpu(howlong=15): 68 | pq = qmnl_straw_man_model_1() 69 | pq.initialize_graph(alternative_codes=[1,2,3,4,5,6]) 70 | pq.graph.add_node(10, children=(5, 6), parameter='MU_nonmotor') 71 | pq.graph.add_node(11, children=(1, 2, 3), parameter='MU_car') 72 | assert( pq.loglike() == approx(-8486.55377320886)) 73 | 74 | import time 75 | starttime = time.time() 76 | while time.time() < starttime + howlong: 77 | dll = pq.d_loglike() 78 | -------------------------------------------------------------------------------- /larch/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = '5.7.2' 3 | 4 | from .util.interface_info import Info, ipython_status 5 | import sys 6 | from .util.styles import css 7 | 8 | info = Info('Larch', False, __version__) 9 | version = Info('Larch', False, __version__, minimal=True) 10 | 11 | 12 | def require_version(n): 13 | from . import __version__ 14 | try: 15 | from packaging import version 16 | except: 17 | def int_from(x): 18 | import re 19 | nums = re.findall(r'\d+', x) 20 | if nums: 21 | return int(nums[0]) 22 | return 0 23 | r = [int_from(i) for i in n.split(".")[:2]] 24 | v = [int_from(i) for i in __version__.split(".")[:2]] 25 | if v[0] > r[0]: 26 | return 27 | if v[0] < r[0]: 28 | raise ValueError("the installed larch is version {}".format(__version__)) 29 | if len(r)>=2: 30 | if v[1] > r[1]: 31 | return 32 | if v[1] < r[1]: 33 | raise ValueError("the installed larch is version {}".format(__version__)) 34 | if len(r)>=3: 35 | if v[2] > r[2]: 36 | return 37 | if v[2] < r[2]: 38 | raise ValueError("the installed larch is version {}".format(__version__)) 39 | else: 40 | if version.parse(n) > version.parse(__version__): 41 | raise ValueError("the installed larch is version {}".format(__version__)) 42 | 43 | 44 | if 'IPython' in ipython_status(): 45 | from .util.display import display 46 | try: 47 | # from .util.styles import stylesheet 48 | # stylesheet() 49 | # display(info) 50 | pass 51 | except: 52 | # print(repr(info)) 53 | jupyter_active = False 54 | else: 55 | jupyter_active = True 56 | else: 57 | jupyter_active = False 58 | # print(repr(info)) 59 | 60 | 61 | from .roles import P, X, PX 62 | from .data_services import DataService 63 | from .omx import OMX 64 | from .data_services.dbf.dbf_reader import DBF 65 | 66 | from .model import Model 67 | from .dataframes import DataFrames 68 | from .dataset import Dataset, DataTree 69 | 70 | from .examples import example 71 | from .util import figures 72 | from .util.excel import ExcelWriter 73 | 74 | _doctest_mode_ = False 75 | 76 | _larch_self = sys.modules[__name__] 77 | 78 | from xmle import Reporter, NumberedCaption 79 | from xmle import load_metadata as read_metadata 80 | from .workspace import make_reporter 81 | -------------------------------------------------------------------------------- /book/intro.md: -------------------------------------------------------------------------------- 1 | # Larch Documentation 2 | 3 | [![conda-forge](https://img.shields.io/conda/vn/conda-forge/larch.svg)](https://anaconda.org/conda-forge/larch) 4 | [![conda-forge](https://img.shields.io/conda/dn/conda-forge/larch)](https://anaconda.org/conda-forge/larch) 5 | [![conda-forge](https://img.shields.io/azure-devops/build/wire-paladin/larch/jpn--.larch/master)](https://dev.azure.com/wire-paladin/larch/_build?definitionId=1&_a=summary&repositoryFilter=1&branchFilter=5%2C5%2C5%2C5%2C5%2C5) 6 | 7 | 🏆︁ Winner of the [AGIFORS 56th Annual Symposium Best Innovation award](http://agifors.org/Symposium). 8 | 9 | This documentation is for the Python interface for Larch. If this is your first go 10 | with Larch, or the first go on a new computer, you might want to start with 11 | [installation](larch-installation). 12 | 13 | **Are you ready for something shiny, new, and *fast*?** Larch is undergoing 14 | a transformation, with a new computational architecture 15 | that can significantly improve performance when working with large datasets. 16 | The old version of Larch used a carefully customized `DataFrames` object to 17 | organize several different aspects of discrete choice data. 18 | The new code uses a more standardized (although still enhanced) `xarray.Dataset` 19 | interface for data, and relies on [numba](https://numba.pydata.org/), 20 | [xarray](https://xarray.pydata.org/en/stable/), and 21 | [sharrow](https://activitysim.github.io/sharrow) to enable super-fast estimation 22 | of choice models. Many (but not yet all) of the core features of Larch have been moved 23 | over to this new platform. 24 | 25 | If you want to try out the new version, just import `larch.numba` instead of `larch` 26 | itself. These docs adopt the convention of `import larch.numba as lx`. All of the 27 | compatible examples in this documentation are being migrated over to the new platform, 28 | but the old examples remain available under the [Legacy Examples](deprecated-examples) 29 | section. If you're not ready for all this awesomeness, or if you need to use some 30 | features of Larch that are not yet in the new version, 31 | **you can still use the legacy (i.e. "old") version of Larch as normal.** 32 | 33 | :::{note} 34 | This project is very much under development. There are plenty of undocumented functions 35 | and features; use them at your own risk. Undocumented features may be non-functional, 36 | not rigorously tested, deprecated or removed without notice in a future version. If a 37 | function or method is documented here, it is intended to be stable in future updates. 38 | ::: 39 | -------------------------------------------------------------------------------- /book/example/legacy/020_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 20: MTC Shared Ride Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 20's nesting structure groups both shared ride alternatives. (`pp. 176 `_) 22 | 23 | .. testcode:: 24 | 25 | shared = m.graph.new_node(parameter='mu', children=[2,3], name='Shared') 26 | 27 | .. testcode:: 28 | 29 | m.ordering = ( 30 | ("CostbyInc","costbyincome", ), 31 | ("TravelTime",".*time.*",".*dist.*", ), 32 | ("Household","hhinc.*","vehbywrk.*", ), 33 | ("Zonal","wkcbd.*","wkempden.*", ), 34 | ("ASCs","ASC.*", ), 35 | ) 36 | 37 | .. doctest:: 38 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 39 | 40 | >>> m.load_data() 41 | >>> m.maximize_loglike('slsqp') 42 | ┣ ...Optimization terminated successfully... 43 | >>> m.loglike() 44 | -3442.414... 45 | 46 | >>> print(m.pfo()[['value']]) 47 | value 48 | Category Parameter 49 | CostbyInc costbyincome -0.0455 50 | TravelTime motorized_time -0.0206 51 | nonmotorized_time -0.0452 52 | motorized_ovtbydist -0.1338 53 | Household hhinc#4 -0.0054 54 | hhinc#5 -0.0089 55 | hhinc#6 -0.0062 56 | vehbywrk_BIKE -0.7031 57 | vehbywrk_SR -0.3147 58 | vehbywrk_TRANSIT -0.9381 59 | vehbywrk_WALK -0.7241 60 | Zonal wkcbd_BIKE 0.5011 61 | wkcbd_SR2 0.3958 62 | wkcbd_SR3 0.6406 63 | wkcbd_TRANSIT 1.3168 64 | wkcbd_WALK 0.1143 65 | wkempden_BIKE 0.0020 66 | wkempden_SR2 0.0019 67 | wkempden_SR3 0.0019 68 | wkempden_TRANSIT 0.0032 69 | wkempden_WALK 0.0030 70 | ASCs ASC_BIKE -1.6199 71 | ASC_SR2 -1.6847 72 | ASC_SR3 -2.2055 73 | ASC_TRANSIT -0.6797 74 | ASC_WALK 0.0732 75 | Other mu 0.3288 76 | -------------------------------------------------------------------------------- /larch/doc/example/020_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 20: MTC Shared Ride Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 20's nesting structure groups both shared ride alternatives. (`pp. 176 `_) 22 | 23 | .. testcode:: 24 | 25 | shared = m.graph.new_node(parameter='mu', children=[2,3], name='Shared') 26 | 27 | .. testcode:: 28 | 29 | m.ordering = ( 30 | ("CostbyInc","costbyincome", ), 31 | ("TravelTime",".*time.*",".*dist.*", ), 32 | ("Household","hhinc.*","vehbywrk.*", ), 33 | ("Zonal","wkcbd.*","wkempden.*", ), 34 | ("ASCs","ASC.*", ), 35 | ) 36 | 37 | .. doctest:: 38 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 39 | 40 | >>> m.load_data() 41 | >>> m.maximize_loglike('slsqp') 42 | ┣ ...Optimization terminated successfully... 43 | >>> m.loglike() 44 | -3442.414... 45 | 46 | >>> print(m.pfo()[['value']]) 47 | value 48 | Category Parameter 49 | CostbyInc costbyincome -0.0455 50 | TravelTime motorized_time -0.0206 51 | nonmotorized_time -0.0452 52 | motorized_ovtbydist -0.1338 53 | Household hhinc#4 -0.0054 54 | hhinc#5 -0.0089 55 | hhinc#6 -0.0062 56 | vehbywrk_BIKE -0.7031 57 | vehbywrk_SR -0.3147 58 | vehbywrk_TRANSIT -0.9381 59 | vehbywrk_WALK -0.7241 60 | Zonal wkcbd_BIKE 0.5011 61 | wkcbd_SR2 0.3958 62 | wkcbd_SR3 0.6406 63 | wkcbd_TRANSIT 1.3168 64 | wkcbd_WALK 0.1143 65 | wkempden_BIKE 0.0020 66 | wkempden_SR2 0.0019 67 | wkempden_SR3 0.0019 68 | wkempden_TRANSIT 0.0032 69 | wkempden_WALK 0.0030 70 | ASCs ASC_BIKE -1.6199 71 | ASC_SR2 -1.6847 72 | ASC_SR3 -2.2055 73 | ASC_TRANSIT -0.6797 74 | ASC_WALK 0.0732 75 | Other mu 0.3288 76 | -------------------------------------------------------------------------------- /book/example/legacy/021_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 21: MTC Non-Motorized Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 21's nesting structure groups the non-motorized alternatives. (`pp. 176 `_) 22 | 23 | .. testcode:: 24 | 25 | nonmotorized = m.graph.new_node(parameter='mu', children=[5,6], name='Nonmotorized') 26 | 27 | .. testcode:: 28 | 29 | m.ordering = ( 30 | ("CostbyInc","costbyincome", ), 31 | ("TravelTime",".*time.*",".*dist.*", ), 32 | ("Household","hhinc.*","vehbywrk.*", ), 33 | ("Zonal","wkcbd.*","wkempden.*", ), 34 | ("ASCs","ASC.*", ), 35 | ) 36 | 37 | .. doctest:: 38 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 39 | 40 | >>> m.load_data() 41 | >>> m.maximize_loglike(method='bhhh') 42 | ┣ ...Optimization terminated successfully... 43 | >>> m.loglike() 44 | -3443.554... 45 | 46 | >>> print(m.pfo()[['value']]) 47 | value 48 | Category Parameter 49 | CostbyInc costbyincome -0.0519 50 | TravelTime motorized_time -0.0199 51 | nonmotorized_time -0.0454 52 | motorized_ovtbydist -0.1351 53 | Household hhinc#4 -0.0053 54 | hhinc#5 -0.0092 55 | hhinc#6 -0.0056 56 | vehbywrk_BIKE -0.6928 57 | vehbywrk_SR -0.3165 58 | vehbywrk_TRANSIT -0.9470 59 | vehbywrk_WALK -0.7141 60 | Zonal wkcbd_BIKE 0.4142 61 | wkcbd_SR2 0.2604 62 | wkcbd_SR3 1.0703 63 | wkcbd_TRANSIT 1.3083 64 | wkcbd_WALK 0.1034 65 | wkempden_BIKE 0.0022 66 | wkempden_SR2 0.0016 67 | wkempden_SR3 0.0023 68 | wkempden_TRANSIT 0.0031 69 | wkempden_WALK 0.0028 70 | ASCs ASC_BIKE -1.4459 71 | ASC_SR2 -1.8088 72 | ASC_SR3 -3.4350 73 | ASC_TRANSIT -0.6817 74 | ASC_WALK 0.0791 75 | Other mu 0.7665 76 | -------------------------------------------------------------------------------- /larch/doc/example/021_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 21: MTC Non-Motorized Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 21's nesting structure groups the non-motorized alternatives. (`pp. 176 `_) 22 | 23 | .. testcode:: 24 | 25 | nonmotorized = m.graph.new_node(parameter='mu', children=[5,6], name='Nonmotorized') 26 | 27 | .. testcode:: 28 | 29 | m.ordering = ( 30 | ("CostbyInc","costbyincome", ), 31 | ("TravelTime",".*time.*",".*dist.*", ), 32 | ("Household","hhinc.*","vehbywrk.*", ), 33 | ("Zonal","wkcbd.*","wkempden.*", ), 34 | ("ASCs","ASC.*", ), 35 | ) 36 | 37 | .. doctest:: 38 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 39 | 40 | >>> m.load_data() 41 | >>> m.maximize_loglike(method='bhhh') 42 | ┣ ...Optimization terminated successfully... 43 | >>> m.loglike() 44 | -3443.554... 45 | 46 | >>> print(m.pfo()[['value']]) 47 | value 48 | Category Parameter 49 | CostbyInc costbyincome -0.0519 50 | TravelTime motorized_time -0.0199 51 | nonmotorized_time -0.0454 52 | motorized_ovtbydist -0.1351 53 | Household hhinc#4 -0.0053 54 | hhinc#5 -0.0092 55 | hhinc#6 -0.0056 56 | vehbywrk_BIKE -0.6928 57 | vehbywrk_SR -0.3165 58 | vehbywrk_TRANSIT -0.9470 59 | vehbywrk_WALK -0.7141 60 | Zonal wkcbd_BIKE 0.4142 61 | wkcbd_SR2 0.2604 62 | wkcbd_SR3 1.0703 63 | wkcbd_TRANSIT 1.3083 64 | wkcbd_WALK 0.1034 65 | wkempden_BIKE 0.0022 66 | wkempden_SR2 0.0016 67 | wkempden_SR3 0.0023 68 | wkempden_TRANSIT 0.0031 69 | wkempden_WALK 0.0028 70 | ASCs ASC_BIKE -1.4459 71 | ASC_SR2 -1.8088 72 | ASC_SR3 -3.4350 73 | ASC_TRANSIT -0.6817 74 | ASC_WALK 0.0791 75 | Other mu 0.7665 76 | -------------------------------------------------------------------------------- /conda-recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: larch 3 | version: "5.7.2" 4 | 5 | source: 6 | path: ../ 7 | 8 | build: 9 | number: 1 10 | skip: True # [py<37] 11 | script: 12 | - "{{ PYTHON }} -m pip install . --no-deps -vv" # [not osx] 13 | - "export SDKROOT=$(xcrun --sdk macosx --show-sdk-path) && export LDFLAGS=-L/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib && {{ PYTHON }} -m pip install . --no-deps -vv" # [osx] 14 | 15 | requirements: 16 | 17 | build: 18 | - python {{ python }} 19 | - pip 20 | - {{ compiler('c') }} 21 | - llvm-openmp # [osx] 22 | - {{ pin_compatible('numpy', upper_bound='1.22') }} 23 | 24 | host: 25 | - python {{ python }} 26 | - pip 27 | - llvm-openmp # [osx] 28 | - cython >=0.29 29 | - {{ pin_compatible('numpy', upper_bound='1.22') }} 30 | 31 | run: 32 | - python {{ python }} 33 | - ipython >=7.1 34 | - llvm-openmp # [osx] 35 | - {{ pin_compatible('numpy', upper_bound='1.22') }} 36 | - scipy >=1.1 37 | - pandas >=0.24,<1.5 38 | - pytables >=3.4.4 # https://github.com/conda-forge/pytables-feedstock/issues/31 39 | - blosc >=1.14.3 40 | - matplotlib >=3.0 41 | - networkx >=2.4 42 | - tqdm >=4.28.1 43 | - cloudpickle >=0.6.1 44 | - appdirs >=1.4 45 | - docutils >=0.13.1 46 | - jinja2 >=2.10 47 | - beautifulsoup4 >=4.6.3 48 | - lxml >=4.2.5 49 | - seaborn >=0.9.0 50 | - scikit-learn >=0.21 51 | - joblib 52 | - pytest >=4.0 53 | - xmle >=0.1.3 54 | - addicty >=2022.2.1 55 | - pyyaml 56 | - pillow 57 | - xlsxwriter 58 | - openpyxl 59 | - pydot 60 | - pyarrow 61 | - numba 62 | - xarray 63 | - sharrow >=2.0 64 | 65 | test: 66 | source_files: 67 | - tests 68 | - book 69 | - pyproject.toml 70 | imports: 71 | - larch 72 | commands: 73 | - pytest 74 | - pytest --pyargs larch 75 | 76 | # You can also put a file called run_test.py in the recipe that will be run 77 | # at test time. 78 | 79 | requires: 80 | # Put any additional test requirements here. For example 81 | - pytest 82 | - pytest-regressions 83 | - sphinx 84 | - sphinx_rtd_theme 85 | - nbsphinx 86 | - geopandas 87 | - nbmake 88 | - tabulate 89 | - zarr 90 | - altair 91 | - apsw 92 | 93 | about: 94 | home: https://larch.newman.me 95 | license: GPLv3 96 | summary: 'Discrete Choice Modeling in Python' 97 | license_family: GPL 98 | 99 | # See 100 | # http://docs.continuum.io/conda/build.html for 101 | # more information about meta.yaml 102 | -------------------------------------------------------------------------------- /larch/doc/example/019_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 19: MTC Private Auto Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 19's nesting structure groups all private automobile alternatives. (`pp. 176 `_) 22 | 23 | .. testcode:: 24 | 25 | private_auto = m.graph.new_node(parameter='mu', children=[1,2,3], name='Motorized') 26 | 27 | m.unmangle(True) 28 | m.set_value('mu',maximum=2.0) 29 | 30 | .. testcode:: 31 | 32 | m.ordering = ( 33 | ("CostbyInc","costbyincome",), 34 | ("TravelTime",".*time.*",".*dist.*", ), 35 | ("Household","hhinc.*","vehbywrk.*",), 36 | ("Zonal","wkcbd.*","wkempden.*",), 37 | ("ASCs","ASC.*",), 38 | ) 39 | 40 | .. doctest:: 41 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 42 | 43 | >>> m.load_data() 44 | >>> m.maximize_loglike(method='bhhh') 45 | ┣ ...Optimization terminated successfully... 46 | >>> m.loglike() 47 | -3435.995... 48 | 49 | >>> print(m.pfo()[['value']]) 50 | value 51 | Category Parameter 52 | CostbyInc costbyincome -0.0607 53 | TravelTime motorized_time -0.0202 54 | nonmotorized_time -0.0462 55 | motorized_ovtbydist -0.1358 56 | Household hhinc#4 -0.0045 57 | hhinc#5 -0.0074 58 | hhinc#6 -0.0050 59 | vehbywrk_BIKE -0.6107 60 | vehbywrk_SR -0.5121 61 | vehbywrk_TRANSIT -0.8725 62 | vehbywrk_WALK -0.6143 63 | Zonal wkcbd_BIKE 0.5234 64 | wkcbd_SR2 0.3982 65 | wkcbd_SR3 1.5869 66 | wkcbd_TRANSIT 1.3667 67 | wkcbd_WALK 0.1170 68 | wkempden_BIKE 0.0023 69 | wkempden_SR2 0.0024 70 | wkempden_SR3 0.0036 71 | wkempden_TRANSIT 0.0035 72 | wkempden_WALK 0.0033 73 | ASCs ASC_BIKE -1.8205 74 | ASC_SR2 -2.5667 75 | ASC_SR3 -4.9623 76 | ASC_TRANSIT -0.8474 77 | ASC_WALK -0.1166 78 | Other mu 1.4656 -------------------------------------------------------------------------------- /book/example/legacy/019_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 19: MTC Private Auto Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 19's nesting structure groups all private automobile alternatives. (`pp. 176 `_) 22 | 23 | .. testcode:: 24 | 25 | private_auto = m.graph.new_node(parameter='mu', children=[1,2,3], name='Motorized') 26 | 27 | m.unmangle(True) 28 | m.set_value('mu',maximum=2.0) 29 | 30 | .. testcode:: 31 | 32 | m.ordering = ( 33 | ("CostbyInc","costbyincome",), 34 | ("TravelTime",".*time.*",".*dist.*", ), 35 | ("Household","hhinc.*","vehbywrk.*",), 36 | ("Zonal","wkcbd.*","wkempden.*",), 37 | ("ASCs","ASC.*",), 38 | ) 39 | 40 | .. doctest:: 41 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 42 | 43 | >>> m.load_data() 44 | >>> m.maximize_loglike(method='bhhh') 45 | ┣ ...Optimization terminated successfully... 46 | >>> m.loglike() 47 | -3435.995... 48 | 49 | >>> print(m.pfo()[['value']]) 50 | value 51 | Category Parameter 52 | CostbyInc costbyincome -0.0607 53 | TravelTime motorized_time -0.0202 54 | nonmotorized_time -0.0462 55 | motorized_ovtbydist -0.1358 56 | Household hhinc#4 -0.0045 57 | hhinc#5 -0.0074 58 | hhinc#6 -0.0050 59 | vehbywrk_BIKE -0.6107 60 | vehbywrk_SR -0.5121 61 | vehbywrk_TRANSIT -0.8725 62 | vehbywrk_WALK -0.6143 63 | Zonal wkcbd_BIKE 0.5234 64 | wkcbd_SR2 0.3982 65 | wkcbd_SR3 1.5869 66 | wkcbd_TRANSIT 1.3667 67 | wkcbd_WALK 0.1170 68 | wkempden_BIKE 0.0023 69 | wkempden_SR2 0.0024 70 | wkempden_SR3 0.0036 71 | wkempden_TRANSIT 0.0035 72 | wkempden_WALK 0.0033 73 | ASCs ASC_BIKE -1.8205 74 | ASC_SR2 -2.5667 75 | ASC_SR3 -4.9623 76 | ASC_TRANSIT -0.8474 77 | ASC_WALK -0.1166 78 | Other mu 1.4656 79 | -------------------------------------------------------------------------------- /larch/util/__init__.py: -------------------------------------------------------------------------------- 1 | from .signal_dict import SignalDict 2 | 3 | import pprint 4 | import datetime 5 | from .addict_yaml import Dict 6 | from .data_expansion import piece, hard_sigmoid 7 | 8 | 9 | def _prettyprint_fallback(x): 10 | if isinstance(x, dictx): 11 | return repr(x) 12 | else: 13 | return pprint.pformat(x) 14 | 15 | 16 | 17 | class dictx(dict): 18 | """Python dict with attribute access and xml output.""" 19 | 20 | def __repr__(self): 21 | if self.keys(): 22 | m = max(map(len, list(str(_) for _ in self.keys()))) + 1 23 | return '\n'.join(['┣'+str(k).rjust(m) + ': ' + _prettyprint_fallback(v).replace('\n','\n┃'+' '*(m+2)) for k, v in self.items()]) 24 | else: 25 | return self.__class__.__name__ + "()" 26 | 27 | def __getattr__(self, item): 28 | if item[0]=="_" and item[-1]=="_": 29 | raise AttributeError(item) 30 | return self.__getitem__(item) 31 | 32 | def __setattr__(self, name, value): 33 | if hasattr(dictx, name): 34 | raise AttributeError("'dictx' object attribute " 35 | "'{0}' is read-only".format(name)) 36 | else: 37 | self[name] = value 38 | 39 | def __xml__(self): 40 | from xmle import Elem, Show 41 | x = Elem('div') 42 | t = x.elem('table', style="margin-top:1px;") 43 | if len(self): 44 | tr = t.elem('tr') 45 | tr.elem('th', text="key") 46 | tr.elem('th', text='value', style='text-align:left;') 47 | for k,v in self.items(): 48 | tr = t.elem('tr') 49 | tr.elem('td', text=str(k)) 50 | try: 51 | if isinstance(v, str) and '\n' not in v and v[:1] != "#": 52 | raise ValueError() 53 | v_ = Show(v) 54 | except (AttributeError, ValueError): 55 | plaintext = pprint.pformat(v) 56 | if isinstance(v, datetime.timedelta): 57 | plaintext = str(v) 58 | if "\n" in plaintext: 59 | tr.elem('td', style='text-align:left;').elem('pre', text=plaintext) 60 | else: 61 | tr.elem('td', style='text-align:left;', text=plaintext) 62 | else: 63 | tr.elem('td', style='text-align:left;') << v_ 64 | else: 65 | tr = t.elem('tr') 66 | tr.elem('td', text="") 67 | return x 68 | 69 | def _repr_html_(self): 70 | return self.__xml__().tostring() 71 | 72 | def copy(self): 73 | return dictx(super().copy()) 74 | 75 | dicta = Dict 76 | 77 | 78 | # Add statistics to pandas.DataFrame and pandas.Series 79 | import pandas 80 | from .statistics import statistics_for_dataframe, statistics_for_array5, uniques, invmap 81 | pandas.DataFrame.statistics = statistics_for_dataframe 82 | pandas.Series.statistics = statistics_for_array5 83 | pandas.Series.uniques = uniques 84 | pandas.Series.invmap = invmap 85 | 86 | from .dataframe import compute 87 | pandas.DataFrame.compute = compute 88 | -------------------------------------------------------------------------------- /book/api/model.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Model 3 | =========== 4 | 5 | .. currentmodule:: larch.numba 6 | 7 | 8 | .. autosummary:: 9 | :toctree: generated/ 10 | 11 | Model 12 | 13 | 14 | Attributes 15 | ========== 16 | 17 | Data Connection 18 | --------------- 19 | 20 | .. autosummary:: 21 | :toctree: generated/ 22 | 23 | Model.datatree 24 | Model.dataset 25 | Model.n_cases 26 | 27 | 28 | Choice Definition 29 | ----------------- 30 | 31 | .. autosummary:: 32 | :toctree: generated/ 33 | 34 | Model.choice_ca_var 35 | Model.choice_co_vars 36 | Model.choice_co_code 37 | 38 | 39 | Alternative Availability 40 | ------------------------ 41 | 42 | .. autosummary:: 43 | :toctree: generated/ 44 | 45 | Model.availability_ca_var 46 | Model.availability_co_vars 47 | 48 | 49 | Utility Definition 50 | ------------------ 51 | 52 | .. autosummary:: 53 | :toctree: generated/ 54 | 55 | Model.utility_ca 56 | Model.utility_co 57 | Model.quantity_ca 58 | 59 | 60 | Parameters 61 | ---------- 62 | 63 | .. autosummary:: 64 | :toctree: generated/ 65 | 66 | Model.pf 67 | 68 | Estimation Results 69 | ------------------ 70 | 71 | .. autosummary:: 72 | :toctree: generated/ 73 | 74 | Model.most_recent_estimation_result 75 | Model.possible_overspecification 76 | 77 | 78 | Methods 79 | ======= 80 | 81 | Setting Parameters 82 | ------------------ 83 | 84 | .. autosummary:: 85 | :toctree: generated/ 86 | 87 | Model.set_values 88 | Model.lock_value 89 | Model.set_cap 90 | Model.remove_unused_parameters 91 | 92 | 93 | Parameter Estimation 94 | -------------------- 95 | 96 | .. autosummary:: 97 | :toctree: generated/ 98 | 99 | Model.maximize_loglike 100 | Model.calculate_parameter_covariance 101 | 102 | 103 | Model Fitness 104 | ------------- 105 | 106 | .. autosummary:: 107 | :toctree: generated/ 108 | 109 | Model.loglike_nil 110 | Model.loglike_null 111 | Model.rho_sq_nil 112 | Model.rho_sq_null 113 | 114 | 115 | Reporting 116 | --------- 117 | 118 | .. autosummary:: 119 | :toctree: generated/ 120 | 121 | Model.parameter_summary 122 | Model.estimation_statistics 123 | Model.to_xlsx 124 | 125 | 126 | Ancillary Computation 127 | --------------------- 128 | 129 | .. autosummary:: 130 | :toctree: generated/ 131 | 132 | Model.bhhh 133 | Model.check_d_loglike 134 | Model.d_loglike 135 | Model.d_loglike_casewise 136 | Model.loglike 137 | Model.loglike_casewise 138 | Model.logsums 139 | Model.probability 140 | Model.quantity 141 | Model.total_weight 142 | Model.utility 143 | -------------------------------------------------------------------------------- /larch/util/png.py: -------------------------------------------------------------------------------- 1 | 2 | import io 3 | import base64 4 | from xmle import Elem 5 | from PIL import Image 6 | 7 | 8 | def make_png( 9 | content, 10 | dpi=None, 11 | compress=True, 12 | output='Elem', 13 | close_after=True, 14 | pad_inches=0.1, 15 | facecolor=None, 16 | return_size=False, 17 | return_dpi=False, 18 | ): 19 | 20 | _bytes_io = None 21 | _img = None 22 | _size = None 23 | 24 | if hasattr(content, '_repr_png_'): 25 | content = content._repr_png_() 26 | 27 | if isinstance(content, bytes): 28 | _bytes_io = io.BytesIO(content) 29 | 30 | if 'matplotlib' in str(type(content)): 31 | from matplotlib import pyplot as plt 32 | import matplotlib.figure 33 | if not isinstance(content, matplotlib.figure.Figure): 34 | try: 35 | content = content.get_figure() 36 | except AttributeError: 37 | if not hasattr(content, 'savefig'): 38 | raise TypeError('matplotlib content must provide `get_figure` or `savefig` method.') 39 | 40 | # content is a Figure or otherwise has a savefig method 41 | try: 42 | fig_number = content.number 43 | except AttributeError: 44 | fig_number = None 45 | 46 | if facecolor is None: 47 | try: 48 | facecolor = content.get_facecolor() 49 | except: 50 | facecolor = 'w' 51 | 52 | try: 53 | edgecolor = content.get_edgecolor() 54 | except: 55 | edgecolor = 'none' 56 | 57 | _bytes_io = io.BytesIO() 58 | content.savefig( 59 | _bytes_io, 60 | dpi=dpi, 61 | orientation='portrait', 62 | format='png', 63 | bbox_inches='tight', 64 | pad_inches=pad_inches, 65 | facecolor=facecolor, 66 | edgecolor=edgecolor, 67 | ) 68 | 69 | if close_after and fig_number is not None: 70 | plt.close(fig_number) 71 | 72 | if _bytes_io.getvalue()[:4] == b'\x89PNG': 73 | _img = Image.open(_bytes_io) 74 | dpi = _img.info.get('dpi', dpi) 75 | if dpi is None: 76 | dpi = [96, 96] 77 | if compress: 78 | _img = _img.convert(mode='P', palette='ADAPTIVE') 79 | _bytes_io = io.BytesIO() 80 | _img.save(_bytes_io, dpi=dpi, format='png') 81 | _size = _img.size 82 | else: 83 | raise ValueError("Not valid PNG data") 84 | 85 | if output.lower() == 'elem': 86 | result = Elem( 87 | tag='img', 88 | src="data:image/png;base64,{}".format(base64.standard_b64encode(_bytes_io.getvalue()).decode()), 89 | ) 90 | elif output.lower() == 'bytesio': 91 | result = _bytes_io 92 | else: 93 | result = _img 94 | 95 | if return_size: 96 | if isinstance(result, tuple): 97 | result = (*result, _size) 98 | else: 99 | result = result, _size 100 | 101 | if return_dpi: 102 | if isinstance(result, tuple): 103 | result = (*result, dpi) 104 | else: 105 | result = result, dpi 106 | 107 | return result 108 | 109 | -------------------------------------------------------------------------------- /larch/doc/example/024_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 23W: MTC Shared Ride - Non-Motorized Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 24's nesting structure groups shared ride and non-motorized alternatives. (`pp. 179 `_) 22 | 23 | .. testcode:: 24 | 25 | shared = m.graph.new_node(parameter='mu_shared', children=[2,3], name='Shared') 26 | nonmotorized = m.graph.new_node(parameter='mu_nonmoto', children=[5,6], name='Nonmotorized') 27 | 28 | .. testcode:: 29 | 30 | m.ordering = ( 31 | ("CostbyInc","costbyincome",), 32 | ("TravelTime",".*time.*",".*dist.*", ), 33 | ("Household","hhinc.*","vehbywrk.*",), 34 | ("Zonal","wkcbd.*","wkempden.*",), 35 | ("ASCs","ASC.*",), 36 | ) 37 | 38 | .. doctest:: 39 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 40 | 41 | >>> m.load_data() 42 | >>> m.maximize_loglike('slsqp') 43 | ┣ ...Optimization terminated successfully... 44 | >>> m.loglike() 45 | -3441.76... 46 | 47 | .. doctest:: 48 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF +SKIP 49 | 50 | >>> print(m.pfo()[['value']]) 51 | value 52 | Category Parameter 53 | CostbyInc costbyincome -0.0450 54 | TravelTime motorized_time -0.0203 55 | nonmotorized_time -0.0452 56 | motorized_ovtbydist -0.1361 57 | Household hhinc#4 -0.0054 58 | hhinc#5 -0.0095 59 | hhinc#6 -0.0058 60 | vehbywrk_BIKE -0.6938 61 | vehbywrk_SR -0.3146 62 | vehbywrk_TRANSIT -0.9388 63 | vehbywrk_WALK -0.7162 64 | Zonal wkcbd_BIKE 0.4252 65 | wkcbd_SR2 0.3973 66 | wkcbd_SR3 0.6389 67 | wkcbd_TRANSIT 1.3162 68 | wkcbd_WALK 0.1162 69 | wkempden_BIKE 0.0023 70 | wkempden_SR2 0.0019 71 | wkempden_SR3 0.0019 72 | wkempden_TRANSIT 0.0032 73 | wkempden_WALK 0.0029 74 | ASCs ASC_BIKE -1.4331 75 | ASC_SR2 -1.6849 76 | ASC_SR3 -2.1988 77 | ASC_TRANSIT -0.6760 78 | ASC_WALK 0.0840 79 | Other mu_nonmoto 0.7622 80 | mu_shared 0.3245 -------------------------------------------------------------------------------- /book/example/legacy/024_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 23W: MTC Shared Ride - Non-Motorized Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 24's nesting structure groups shared ride and non-motorized alternatives. (`pp. 179 `_) 22 | 23 | .. testcode:: 24 | 25 | shared = m.graph.new_node(parameter='mu_shared', children=[2,3], name='Shared') 26 | nonmotorized = m.graph.new_node(parameter='mu_nonmoto', children=[5,6], name='Nonmotorized') 27 | 28 | .. testcode:: 29 | 30 | m.ordering = ( 31 | ("CostbyInc","costbyincome",), 32 | ("TravelTime",".*time.*",".*dist.*", ), 33 | ("Household","hhinc.*","vehbywrk.*",), 34 | ("Zonal","wkcbd.*","wkempden.*",), 35 | ("ASCs","ASC.*",), 36 | ) 37 | 38 | .. doctest:: 39 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 40 | 41 | >>> m.load_data() 42 | >>> m.maximize_loglike('slsqp') 43 | ┣ ...Optimization terminated successfully... 44 | >>> m.loglike() 45 | -3441.76... 46 | 47 | .. doctest:: 48 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF +SKIP 49 | 50 | >>> print(m.pfo()[['value']]) 51 | value 52 | Category Parameter 53 | CostbyInc costbyincome -0.0450 54 | TravelTime motorized_time -0.0203 55 | nonmotorized_time -0.0452 56 | motorized_ovtbydist -0.1361 57 | Household hhinc#4 -0.0054 58 | hhinc#5 -0.0095 59 | hhinc#6 -0.0058 60 | vehbywrk_BIKE -0.6938 61 | vehbywrk_SR -0.3146 62 | vehbywrk_TRANSIT -0.9388 63 | vehbywrk_WALK -0.7162 64 | Zonal wkcbd_BIKE 0.4252 65 | wkcbd_SR2 0.3973 66 | wkcbd_SR3 0.6389 67 | wkcbd_TRANSIT 1.3162 68 | wkcbd_WALK 0.1162 69 | wkempden_BIKE 0.0023 70 | wkempden_SR2 0.0019 71 | wkempden_SR3 0.0019 72 | wkempden_TRANSIT 0.0032 73 | wkempden_WALK 0.0029 74 | ASCs ASC_BIKE -1.4331 75 | ASC_SR2 -1.6849 76 | ASC_SR3 -2.1988 77 | ASC_TRANSIT -0.6760 78 | ASC_WALK 0.0840 79 | Other mu_nonmoto 0.7622 80 | mu_shared 0.3245 81 | -------------------------------------------------------------------------------- /larch/doc/example/023_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 23W: MTC Private Auto - Non-Motorized Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 23's nesting structure groups private automobiles and non-motorized alternatives. (`pp. 179 `_) 22 | 23 | .. testcode:: 24 | 25 | private_auto = m.graph.new_node(parameter='mu_auto', children=[1,2,3], name='Private_Auto') 26 | nonmotorized = m.graph.new_node(parameter='mu_nonmoto', children=[5,6], name='Nonmotorized') 27 | 28 | m.unmangle(True) 29 | m.set_value('mu_auto',maximum=2.0) 30 | 31 | .. testcode:: 32 | 33 | m.ordering = ( 34 | ("CostbyInc","costbyincome",), 35 | ("TravelTime",".*time.*",".*dist.*", ), 36 | ("Household","hhinc.*","vehbywrk.*",), 37 | ("Zonal","wkcbd.*","wkempden.*",), 38 | ("ASCs","ASC.*",), 39 | ) 40 | 41 | .. doctest:: 42 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 43 | 44 | >>> m.load_data() 45 | >>> m.maximize_loglike(method='bhhh') 46 | ┣ ...Optimization terminated successfully... 47 | >>> m.loglike() 48 | -3435.357... 49 | 50 | >>> print(m.pfo()[['value']]) 51 | value 52 | Category Parameter 53 | CostbyInc costbyincome -0.0600 54 | TravelTime motorized_time -0.0199 55 | nonmotorized_time -0.0460 56 | motorized_ovtbydist -0.1383 57 | Household hhinc#4 -0.0046 58 | hhinc#5 -0.0081 59 | hhinc#6 -0.0046 60 | vehbywrk_BIKE -0.5952 61 | vehbywrk_SR -0.5120 62 | vehbywrk_TRANSIT -0.8737 63 | vehbywrk_WALK -0.6076 64 | Zonal wkcbd_BIKE 0.4459 65 | wkcbd_SR2 0.3987 66 | wkcbd_SR3 1.5877 67 | wkcbd_TRANSIT 1.3660 68 | wkcbd_WALK 0.1202 69 | wkempden_BIKE 0.0026 70 | wkempden_SR2 0.0025 71 | wkempden_SR3 0.0036 72 | wkempden_TRANSIT 0.0035 73 | wkempden_WALK 0.0032 74 | ASCs ASC_BIKE -1.6467 75 | ASC_SR2 -2.5664 76 | ASC_SR3 -4.9614 77 | ASC_TRANSIT -0.8427 78 | ASC_WALK -0.1102 79 | Other mu_auto 1.4652 80 | mu_nonmoto 0.7661 -------------------------------------------------------------------------------- /book/example/legacy/023_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 23W: MTC Private Auto - Non-Motorized Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 23's nesting structure groups private automobiles and non-motorized alternatives. (`pp. 179 `_) 22 | 23 | .. testcode:: 24 | 25 | private_auto = m.graph.new_node(parameter='mu_auto', children=[1,2,3], name='Private_Auto') 26 | nonmotorized = m.graph.new_node(parameter='mu_nonmoto', children=[5,6], name='Nonmotorized') 27 | 28 | m.unmangle(True) 29 | m.set_value('mu_auto',maximum=2.0) 30 | 31 | .. testcode:: 32 | 33 | m.ordering = ( 34 | ("CostbyInc","costbyincome",), 35 | ("TravelTime",".*time.*",".*dist.*", ), 36 | ("Household","hhinc.*","vehbywrk.*",), 37 | ("Zonal","wkcbd.*","wkempden.*",), 38 | ("ASCs","ASC.*",), 39 | ) 40 | 41 | .. doctest:: 42 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 43 | 44 | >>> m.load_data() 45 | >>> m.maximize_loglike(method='bhhh') 46 | ┣ ...Optimization terminated successfully... 47 | >>> m.loglike() 48 | -3435.357... 49 | 50 | >>> print(m.pfo()[['value']]) 51 | value 52 | Category Parameter 53 | CostbyInc costbyincome -0.0600 54 | TravelTime motorized_time -0.0199 55 | nonmotorized_time -0.0460 56 | motorized_ovtbydist -0.1383 57 | Household hhinc#4 -0.0046 58 | hhinc#5 -0.0081 59 | hhinc#6 -0.0046 60 | vehbywrk_BIKE -0.5952 61 | vehbywrk_SR -0.5120 62 | vehbywrk_TRANSIT -0.8737 63 | vehbywrk_WALK -0.6076 64 | Zonal wkcbd_BIKE 0.4459 65 | wkcbd_SR2 0.3987 66 | wkcbd_SR3 1.5877 67 | wkcbd_TRANSIT 1.3660 68 | wkcbd_WALK 0.1202 69 | wkempden_BIKE 0.0026 70 | wkempden_SR2 0.0025 71 | wkempden_SR3 0.0036 72 | wkempden_TRANSIT 0.0035 73 | wkempden_WALK 0.0032 74 | ASCs ASC_BIKE -1.6467 75 | ASC_SR2 -2.5664 76 | ASC_SR3 -4.9614 77 | ASC_TRANSIT -0.8427 78 | ASC_WALK -0.1102 79 | Other mu_auto 1.4652 80 | mu_nonmoto 0.7661 81 | -------------------------------------------------------------------------------- /.github/workflows/dev-docs.yml: -------------------------------------------------------------------------------- 1 | name: dev-docs 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | deploy-docs: 10 | runs-on: ubuntu-latest 11 | environment: developer 12 | defaults: 13 | run: 14 | shell: bash -l {0} 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | path: larch 20 | - name: Set up Python 3.9 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: 3.9 24 | - name: Cache conda 25 | uses: actions/cache@v2 26 | env: 27 | # Increase this value to reset cache if larch/environments/development.yml has not changed 28 | CACHE_NUMBER: 0 29 | with: 30 | path: ~/conda_pkgs_dir 31 | key: 32 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ hashFiles('larch/environments/development.yml') }} 33 | - name: Install dependencies 34 | uses: conda-incubator/setup-miniconda@v2 35 | with: 36 | miniforge-variant: Mambaforge 37 | miniforge-version: latest 38 | use-mamba: true 39 | environment-file: larch/environments/development.yml 40 | python-version: 3.9 41 | activate-environment: development 42 | auto-activate-base: false 43 | auto-update-conda: false 44 | use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly! 45 | - name: Infer larch version 46 | run: | 47 | python larch/tools/get_version.py LARCH_VERSION.txt 48 | cat LARCH_VERSION.txt >> $GITHUB_ENV 49 | - name: Install sharrow 50 | run: | 51 | git clone https://github.com/ActivitySim/sharrow.git 52 | cd sharrow 53 | python -m pip install --no-deps -e . 54 | - name: Install larch 55 | run: | 56 | cd larch 57 | python -m pip install --no-deps -e . 58 | - name: Conda checkup 59 | run: | 60 | conda info -a 61 | conda list 62 | - name: Build the docs 63 | run: | 64 | python larch/book/_scripts/hide_test_cells.py 65 | python larch/book/_scripts/developer_doc_title.py v${{ env.LARCH_VERSION }} 66 | jb build larch/book 67 | - name: Push to GitHub Pages 68 | uses: peaceiris/actions-gh-pages@v3.8.0 69 | with: 70 | github_token: ${{ secrets.GITHUB_TOKEN }} 71 | # Token is created automatically by Github Actions, no other config needed 72 | publish_dir: larch/book/_build/html 73 | - name: Push to External GitHub Pages 74 | uses: peaceiris/actions-gh-pages@v3.8.0 75 | if: "!contains(env.LARCH_VERSION, '+')" 76 | with: 77 | personal_token: ${{ secrets.PERSONAL_TOKEN }} 78 | publish_dir: larch/book/_build/html 79 | external_repository: jpndashdash/larchdocs 80 | destination_dir: v${{ env.LARCH_VERSION }} 81 | -------------------------------------------------------------------------------- /book/example/legacy/025_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 25: MTC Private Auto - Motorized Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 25's nesting structure groups motorized and shared ride alternatives. (`pp. 182 `_) 22 | 23 | Since private auto is completely contained within motorized, we can use the private nest when we define the motorized nest. 24 | 25 | 26 | .. testcode:: 27 | 28 | private_auto = m.graph.new_node(parameter='mu_private', children=[1,2,3], name='Private_Auto') 29 | motorized = m.graph.new_node(parameter='mu_moto', children=[private_auto,4], name='Motorized') 30 | 31 | .. testcode:: 32 | 33 | m.ordering = ( 34 | ("CostbyInc","costbyincome",), 35 | ("TravelTime",".*time.*",".*dist.*", ), 36 | ("Household","hhinc.*","vehbywrk.*",), 37 | ("Zonal","wkcbd.*","wkempden.*",), 38 | ("ASCs","ASC.*",), 39 | ) 40 | 41 | .. doctest:: 42 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 43 | 44 | >>> m.load_data() 45 | >>> m.maximize_loglike(method='bhhh') 46 | ┣ ...Optimization terminated successfully... 47 | >>> m.loglike() 48 | -3427.166... 49 | 50 | >>> print(m.pfo()[['value']]) 51 | value 52 | Category Parameter 53 | CostbyInc costbyincome -0.0363 54 | TravelTime motorized_time -0.0106 55 | nonmotorized_time -0.0471 56 | motorized_ovtbydist -0.0995 57 | Household hhinc#4 -0.0022 58 | hhinc#5 -0.0090 59 | hhinc#6 -0.0061 60 | vehbywrk_BIKE -0.6885 61 | vehbywrk_SR -0.3218 62 | vehbywrk_TRANSIT -0.4628 63 | vehbywrk_WALK -0.7052 64 | Zonal wkcbd_BIKE 0.5003 65 | wkcbd_SR2 0.2753 66 | wkcbd_SR3 1.0253 67 | wkcbd_TRANSIT 0.7310 68 | wkcbd_WALK 0.1416 69 | wkempden_BIKE 0.0014 70 | wkempden_SR2 0.0016 71 | wkempden_SR3 0.0024 72 | wkempden_TRANSIT 0.0019 73 | wkempden_WALK 0.0022 74 | ASCs ASC_BIKE -1.3790 75 | ASC_SR2 -1.6337 76 | ASC_SR3 -3.1469 77 | ASC_TRANSIT -0.4069 78 | ASC_WALK 0.3391 79 | Other mu_moto 0.5322 80 | mu_private 0.9236 81 | -------------------------------------------------------------------------------- /larch/doc/example/025_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | =========================================================== 4 | 25: MTC Private Auto - Motorized Nested Mode Choice 5 | =========================================================== 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | m = larch.example(17) 20 | 21 | Model 25's nesting structure groups motorized and shared ride alternatives. (`pp. 182 `_) 22 | 23 | Since private auto is completely contained within motorized, we can use the private nest when we define the motorized nest. 24 | 25 | 26 | .. testcode:: 27 | 28 | private_auto = m.graph.new_node(parameter='mu_private', children=[1,2,3], name='Private_Auto') 29 | motorized = m.graph.new_node(parameter='mu_moto', children=[private_auto,4], name='Motorized') 30 | 31 | .. testcode:: 32 | 33 | m.ordering = ( 34 | ("CostbyInc","costbyincome",), 35 | ("TravelTime",".*time.*",".*dist.*", ), 36 | ("Household","hhinc.*","vehbywrk.*",), 37 | ("Zonal","wkcbd.*","wkempden.*",), 38 | ("ASCs","ASC.*",), 39 | ) 40 | 41 | .. doctest:: 42 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 43 | 44 | >>> m.load_data() 45 | >>> m.maximize_loglike(method='bhhh') 46 | ┣ ...Optimization terminated successfully... 47 | >>> m.loglike() 48 | -3427.166... 49 | 50 | >>> print(m.pfo()[['value']]) 51 | value 52 | Category Parameter 53 | CostbyInc costbyincome -0.0363 54 | TravelTime motorized_time -0.0106 55 | nonmotorized_time -0.0471 56 | motorized_ovtbydist -0.0995 57 | Household hhinc#4 -0.0022 58 | hhinc#5 -0.0090 59 | hhinc#6 -0.0061 60 | vehbywrk_BIKE -0.6885 61 | vehbywrk_SR -0.3218 62 | vehbywrk_TRANSIT -0.4628 63 | vehbywrk_WALK -0.7052 64 | Zonal wkcbd_BIKE 0.5003 65 | wkcbd_SR2 0.2753 66 | wkcbd_SR3 1.0253 67 | wkcbd_TRANSIT 0.7310 68 | wkcbd_WALK 0.1416 69 | wkempden_BIKE 0.0014 70 | wkempden_SR2 0.0016 71 | wkempden_SR3 0.0024 72 | wkempden_TRANSIT 0.0019 73 | wkempden_WALK 0.0022 74 | ASCs ASC_BIKE -1.3790 75 | ASC_SR2 -1.6337 76 | ASC_SR3 -3.1469 77 | ASC_TRANSIT -0.4069 78 | ASC_WALK 0.3391 79 | Other mu_moto 0.5322 80 | mu_private 0.9236 -------------------------------------------------------------------------------- /larch/dataset/patch.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import xarray as xr 4 | from typing import Mapping 5 | import warnings 6 | 7 | from sharrow.accessors import register_dataset_method, register_dataarray_method 8 | 9 | 10 | class NameConflictWarning(Warning): 11 | """Warning for conflicts in name registration.""" 12 | 13 | 14 | def _register_classmethod(name, cls): 15 | def decorator(classmeth): 16 | if hasattr(cls, name): 17 | warnings.warn( 18 | f"registration of classmethod {classmeth!r} under name {name!r} " 19 | f"for type {cls!r} is overriding a preexisting attribute with " 20 | f"the same name.", 21 | NameConflictWarning, 22 | stacklevel=2, 23 | ) 24 | setattr(cls, name, classmethod(classmeth)) 25 | return classmeth 26 | 27 | return decorator 28 | 29 | 30 | def register_dataarray_classmethod(func): 31 | """ 32 | Register a custom classmethod on xarray.DataArray objects. 33 | 34 | Use this as a decorator to add class methods. 35 | 36 | Parameters 37 | ---------- 38 | func : Callable 39 | Class method to add. The name is inferred from the 40 | original name of this function. 41 | """ 42 | return _register_classmethod(func.__name__, xr.DataArray)(func) 43 | 44 | 45 | def register_dataset_classmethod(func): 46 | """ 47 | Register a custom classmethod on xarray.Dataset objects. 48 | 49 | Use this as a decorator to add class methods. 50 | 51 | Parameters 52 | ---------- 53 | func : Callable 54 | Class method to add. The name is inferred from the 55 | original name of this function. 56 | """ 57 | return _register_classmethod(func.__name__, xr.Dataset)(func) 58 | 59 | 60 | @register_dataset_method 61 | def set_dtypes(self, dtypes, inplace=False, on_error='warn'): 62 | """ 63 | Set the dtypes for the variables in this Dataset. 64 | 65 | Parameters 66 | ---------- 67 | dtypes : Mapping or DataFrame 68 | Mapping of names to dtypes, or a DataFrame to infer such a 69 | mapping. 70 | inplace : bool, default False 71 | Whether to convert dtypes inplace. 72 | on_error : {'warn', 'raise', 'ignore'} 73 | What to do when a type conversion triggers an error. 74 | 75 | Returns 76 | ------- 77 | Dataset 78 | """ 79 | if isinstance(dtypes, pd.DataFrame): 80 | dtypes = dtypes.dtypes 81 | if inplace: 82 | obj = self 83 | else: 84 | obj = self.copy() 85 | for k in obj: 86 | if k not in dtypes: 87 | continue 88 | try: 89 | obj[k] = obj[k].astype(dtypes[k]) 90 | except Exception as err: 91 | if on_error == 'warn': 92 | warnings.warn(f"{err!r} on converting {k}") 93 | elif on_error == 'raise': 94 | raise 95 | return obj 96 | -------------------------------------------------------------------------------- /larch/doc/index.rst: -------------------------------------------------------------------------------- 1 | .. larch documentation master file 2 | 3 | ============================== 4 | |treelogo| Larch Documentation 5 | ============================== 6 | 7 | .. image:: https://img.shields.io/conda/vn/conda-forge/larch.svg 8 | :target: https://anaconda.org/conda-forge/larch 9 | :class: statusbadge 10 | 11 | .. image:: https://img.shields.io/conda/dn/conda-forge/larch 12 | :target: https://anaconda.org/conda-forge/larch 13 | :class: statusbadge 14 | 15 | .. image:: https://img.shields.io/badge/source-github-yellow.svg 16 | :target: https://github.com/jpn--/larch 17 | :class: statusbadge 18 | 19 | .. image:: https://img.shields.io/appveyor/ci/jpn--/larch.svg?label=windows%20build 20 | :target: https://ci.appveyor.com/project/jpn--/larch 21 | :class: statusbadge 22 | 23 | .. image:: https://img.shields.io/travis/jpn--/larch.svg?label=linux%20build 24 | :target: https://travis-ci.org/jpn--/larch 25 | :class: statusbadge 26 | 27 | .. |treelogo| image:: ../../img/larch_favicon.png 28 | :height: 0.9em 29 | :class: treelogo 30 | 31 | .. |trophy| image:: ../../img/trophy-64.png 32 | :class: trophyicon 33 | 34 | 35 | .. 36 | 37 | |trophy| Winner of the `AGIFORS 56th Annual Symposium Best Innovation award `__. 38 | 39 | This documentation is for the Python interface for Larch. If this is your first go 40 | with Larch, or the first go on a new computer, you might want to start with :ref:`installation`. 41 | 42 | This project is very much under development. There are plenty of undocumented functions 43 | and features; use them at your own risk. Undocumented features may be non-functional, 44 | not rigorously tested, deprecated or removed without notice in a future version. If a 45 | function or method is documented here, it is intended to be stable in future updates. 46 | 47 | You may also find these links useful: 48 | 49 | * `Python `_ 3.7: http://docs.python.org/3.7/index.html 50 | * `NumPy `__: http://docs.scipy.org/doc/numpy/ 51 | * `SciPy `__: http://docs.scipy.org/doc/scipy/reference/ 52 | * `scikit-learn `__: https://scikit-learn.org/stable/documentation.html 53 | 54 | For learning Python itself: 55 | 56 | * If you are new to Python but have some experience with some other programming language, 57 | there is an online quick introduction `Python for Transportation Modeling `__, 58 | available free online, sponsored by the Florida Department of Transportation. 59 | * If you are new to programming in general, there are better (slower) guides at 60 | `Learning Python `__ 61 | 62 | 63 | 64 | Contents 65 | ======== 66 | 67 | .. toctree:: 68 | :maxdepth: 4 69 | 70 | getting-started 71 | data 72 | model 73 | latentclass 74 | math 75 | machine-learning/index 76 | example/examples 77 | 78 | 79 | -------------------------------------------------------------------------------- /larch/data_services/h5/h5pod/idga.py: -------------------------------------------------------------------------------- 1 | 2 | from .generic import * 3 | from tables import NoSuchNodeError 4 | 5 | class H5PodGA(H5Pod): 6 | """ 7 | A HDF5 group node containing :ref:`idga` format data. 8 | 9 | The GA pod is conceptually similar to the CA pod, although cases are included 10 | by a lookup key instead of individually. Each array of data is stored as an array, where 11 | each array has rows of values attributable to arbitrary groups of casewise observations, 12 | and columns attributable to individual choice alternatives. The common use case is an OMX 13 | matrix file containing skims, where the case-wise observations each pull a single row 14 | from the skims. 15 | 16 | The H5PodGA has one special attribute , naming the row-index to use for each case. This 17 | name must exist in :ref:`idco` data. 18 | 19 | """ 20 | 21 | def __init__(self, filename, rowindexes, *args, **kwargs): 22 | super().__init__(*args, filename=filename, **kwargs) 23 | if isinstance(rowindexes, str): 24 | self.rowindexes = self._groupnode._v_children[rowindexes][:] 25 | elif isinstance(rowindexes, numpy.ndarray): 26 | self.rowindexes = rowindexes.squeeze() 27 | else: 28 | raise TypeError('rowindexes must be str or ndarray') 29 | 30 | @property 31 | def podtype(self): 32 | return 'idga' 33 | 34 | 35 | def __getitem__(self, item): 36 | 37 | if isinstance(item, tuple) and len(item)>=2 and isinstance(item[-1], slice): 38 | names, slice_ = item[:-1], item[-1] 39 | else: 40 | names = item 41 | slice_ = None 42 | 43 | # convert a single name string to a one item list 44 | if isinstance(names, str): 45 | names = [names, ] 46 | 47 | dtype = numpy.float64 48 | 49 | result = numpy.zeros( [selector_len_for(slice_, self.shape[0]), *self.shape[1:], len(names)], dtype=dtype) 50 | 51 | for i, cmd in enumerate(names): 52 | temp = self._evaluate_single_item(cmd, None) 53 | if slice_ is not None: 54 | result[...,i] = temp[self.rowindexes[slice_],:] 55 | else: 56 | result[...,i] = temp[self.rowindexes,:] 57 | return result 58 | 59 | @property 60 | def shape(self): 61 | """The shape of the pod. 62 | 63 | """ 64 | return tuple(self.rowindexes.shape) + self.metashape[1:] 65 | 66 | @property 67 | def metashape(self): 68 | """The shape of the underlying skims. 69 | 70 | """ 71 | try: 72 | return tuple(self._groupnode._v_attrs.SHAPE) 73 | except (AttributeError, NoSuchNodeError): 74 | return tuple(self._groupnode._v_parent._v_attrs.SHAPE) 75 | 76 | def load_data_item(self, name, result, selector=None): 77 | """Load a slice of the pod arrays into an array in memory""" 78 | # convert a single name string to a one item list 79 | 80 | from ...general import _sqz_same 81 | _sqz_same(result.shape, [selector_len_for(selector, self.shape[0]), *self.shape[1:]]) 82 | 83 | temp = self._evaluate_single_item(name, None) 84 | if selector is not None: 85 | result[:] = temp[self.rowindexes[selector], :] 86 | else: 87 | result[:] = temp[self.rowindexes, :] 88 | return result 89 | 90 | -------------------------------------------------------------------------------- /book/example/legacy/003_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ============================================ 4 | 3: MTC MNL Mode Choice, Zeroed Shared 5 | ============================================ 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | d = larch.examples.MTC() 20 | m = larch.Model(dataservice=d) 21 | 22 | Model 3 posits the effect of income on all the automobile modes (drive alone, shared ride 2, and shared ride 3+) 23 | is the same, but the effect is different for the other modes. We include this 24 | constraint by setting the income coefficients in the utilities of the automobile modes to be equal. 25 | In this case, we set them to zero since drive alone is the reference mode. (`pp. 108 `_) 26 | 27 | We set the terms to zero can be done by omitting the terms altogether. 28 | 29 | .. testcode:: 30 | 31 | from larch.roles import P, X, PX 32 | m.utility_co[2] = P("ASC_SR2") 33 | m.utility_co[3] = P("ASC_SR3P") 34 | m.utility_co[4] = P("ASC_TRAN") + P("hhinc#4") * X("hhinc") 35 | m.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 36 | m.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 37 | 38 | .. testcode:: 39 | 40 | m.utility_ca = PX("tottime") + PX("totcost") 41 | 42 | .. testcode:: 43 | 44 | m.availability_var = '_avail_' 45 | m.choice_ca_var = '_choice_' 46 | 47 | .. testcode:: 48 | 49 | m.ordering = ( 50 | ("LOS", "totcost", "tottime", ), 51 | ("Income", "hhinc.*", ), 52 | ("ASCs", "ASC.*", ), 53 | ) 54 | 55 | .. testcode:: 56 | 57 | m.title = "MTC Example 3, Zeroed Shared" 58 | 59 | .. doctest:: 60 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 61 | 62 | >>> m.load_data() 63 | >>> m.maximize_loglike() 64 | ┣ ...Optimization terminated successfully... 65 | >>> m.calculate_parameter_covariance() 66 | >>> m.loglike() 67 | -3627.233... 68 | 69 | >>> print(m.pfo()[['value','std_err','t_stat','robust_std_err','robust_t_stat']]) 70 | value std_err t_stat robust_std_err robust_t_stat 71 | Category Parameter 72 | LOS totcost -0.0049 0.0002 -20.6062 0.0003 -17.3891 73 | tottime -0.0513 0.0031 -16.5594 0.0035 -14.8580 74 | Income hhinc#4 -0.0049 0.0018 -2.7295 0.0018 -2.7644 75 | hhinc#5 -0.0125 0.0053 -2.3451 0.0065 -1.9014 76 | hhinc#6 -0.0093 0.0030 -3.0977 0.0032 -2.9143 77 | ASCs ASC_BIKE -2.3981 0.3038 -7.8934 0.3600 -6.6622 78 | ASC_SR2 -2.3043 0.0547 -42.1387 0.0574 -40.1180 79 | ASC_SR3P -3.7036 0.0930 -39.8127 0.0950 -38.9751 80 | ASC_TRAN -0.6975 0.1304 -5.3504 0.1277 -5.4600 81 | ASC_WALK -0.2292 0.1933 -1.1857 0.2057 -1.1141 82 | -------------------------------------------------------------------------------- /larch/doc/example/003_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ============================================ 4 | 3: MTC MNL Mode Choice, Zeroed Shared 5 | ============================================ 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | .. testcode:: 18 | 19 | d = larch.examples.MTC() 20 | m = larch.Model(dataservice=d) 21 | 22 | Model 3 posits the effect of income on all the automobile modes (drive alone, shared ride 2, and shared ride 3+) 23 | is the same, but the effect is different for the other modes. We include this 24 | constraint by setting the income coefficients in the utilities of the automobile modes to be equal. 25 | In this case, we set them to zero since drive alone is the reference mode. (`pp. 108 `_) 26 | 27 | We set the terms to zero can be done by omitting the terms altogether. 28 | 29 | .. testcode:: 30 | 31 | from larch.roles import P, X, PX 32 | m.utility_co[2] = P("ASC_SR2") 33 | m.utility_co[3] = P("ASC_SR3P") 34 | m.utility_co[4] = P("ASC_TRAN") + P("hhinc#4") * X("hhinc") 35 | m.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 36 | m.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 37 | 38 | .. testcode:: 39 | 40 | m.utility_ca = PX("tottime") + PX("totcost") 41 | 42 | .. testcode:: 43 | 44 | m.availability_var = '_avail_' 45 | m.choice_ca_var = '_choice_' 46 | 47 | .. testcode:: 48 | 49 | m.ordering = ( 50 | ("LOS", "totcost", "tottime", ), 51 | ("Income", "hhinc.*", ), 52 | ("ASCs", "ASC.*", ), 53 | ) 54 | 55 | .. testcode:: 56 | 57 | m.title = "MTC Example 3, Zeroed Shared" 58 | 59 | .. doctest:: 60 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 61 | 62 | >>> m.load_data() 63 | >>> m.maximize_loglike() 64 | ┣ ...Optimization terminated successfully... 65 | >>> m.calculate_parameter_covariance() 66 | >>> m.loglike() 67 | -3627.233... 68 | 69 | >>> print(m.pfo()[['value','std_err','t_stat','robust_std_err','robust_t_stat']]) 70 | value std_err t_stat robust_std_err robust_t_stat 71 | Category Parameter 72 | LOS totcost -0.0049 0.0002 -20.6062 0.0003 -17.3891 73 | tottime -0.0513 0.0031 -16.5594 0.0035 -14.8580 74 | Income hhinc#4 -0.0049 0.0018 -2.7295 0.0018 -2.7644 75 | hhinc#5 -0.0125 0.0053 -2.3451 0.0065 -1.9014 76 | hhinc#6 -0.0093 0.0030 -3.0977 0.0032 -2.9143 77 | ASCs ASC_BIKE -2.3981 0.3038 -7.8934 0.3600 -6.6622 78 | ASC_SR2 -2.3043 0.0547 -42.1387 0.0574 -40.1180 79 | ASC_SR3P -3.7036 0.0930 -39.8127 0.0950 -38.9751 80 | ASC_TRAN -0.6975 0.1304 -5.3504 0.1277 -5.4600 81 | ASC_WALK -0.2292 0.1933 -1.1857 0.2057 -1.1141 82 | -------------------------------------------------------------------------------- /larch/model/cast.h: -------------------------------------------------------------------------------- 1 | /*=====================================================================* 2 | * Copyright (C) 2012 Paul Mineiro * 3 | * All rights reserved. * 4 | * * 5 | * Redistribution and use in source and binary forms, with * 6 | * or without modification, are permitted provided that the * 7 | * following conditions are met: * 8 | * * 9 | * * Redistributions of source code must retain the * 10 | * above copyright notice, this list of conditions and * 11 | * the following disclaimer. * 12 | * * 13 | * * Redistributions in binary form must reproduce the * 14 | * above copyright notice, this list of conditions and * 15 | * the following disclaimer in the documentation and/or * 16 | * other materials provided with the distribution. * 17 | * * 18 | * * Neither the name of Paul Mineiro nor the names * 19 | * of other contributors may be used to endorse or promote * 20 | * products derived from this software without specific * 21 | * prior written permission. * 22 | * * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * 24 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * 25 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * 26 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 28 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * 29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * 30 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * 31 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * 32 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * 33 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 34 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * 35 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * 36 | * POSSIBILITY OF SUCH DAMAGE. * 37 | * * 38 | * Contact: Paul Mineiro * 39 | *=====================================================================*/ 40 | 41 | #ifndef __CAST_H_ 42 | 43 | #ifdef __cplusplus 44 | #define cast_uint32_t static_cast 45 | #else 46 | #define cast_uint32_t (uint32_t) 47 | #endif 48 | 49 | #endif // __CAST_H_ 50 | -------------------------------------------------------------------------------- /book/example/legacy/009_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ================================================ 4 | 9: MTC MNL Mode Choice, TTR = 4.0 5 | ================================================ 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | Model 9 is formulated the same way as Model 8, only with the TTR set to 4. (`pp. 114 `_) 18 | 19 | .. testcode:: 20 | 21 | d = larch.examples.MTC() 22 | m = larch.Model(dataservice=d) 23 | 24 | .. testcode:: 25 | 26 | from larch.roles import P, X, PX 27 | m.utility_co[2] = P("ASC_SR2") + P("hhinc#2,3") * X("hhinc") 28 | m.utility_co[3] = P("ASC_SR3P") + P("hhinc#2,3") * X("hhinc") 29 | m.utility_co[4] = P("ASC_TRAN") + P("hhinc#4") * X("hhinc") 30 | m.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 31 | m.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 32 | 33 | .. testcode:: 34 | 35 | m.utility_ca = ( 36 | + P("nonmotorized_time") * X("(altnum>4) * tottime") 37 | + P("motorized_time") * (X("(altnum <= 4) * ivtt") 38 | + 4 * X("(altnum <= 4) * ovtt")) + PX("totcost") 39 | ) 40 | 41 | .. testcode:: 42 | 43 | m.availability_var = '_avail_' 44 | m.choice_ca_var = '_choice_' 45 | 46 | .. testcode:: 47 | 48 | m.ordering = ( 49 | ("LOS", ".*cost.*", ".*time.*", ".*ivtt.*", ), 50 | ("Income", "hhinc.*", ), 51 | ("ASCs", "ASC.*", ), 52 | ) 53 | 54 | .. doctest:: 55 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 56 | 57 | >>> m.load_data() 58 | >>> m.maximize_loglike() 59 | ┣ ...Optimization terminated successfully... 60 | >>> m.calculate_parameter_covariance() 61 | >>> m.loglike() 62 | -3590.916... 63 | 64 | >>> print(m.pfo()[['value','std_err','t_stat','robust_std_err','robust_t_stat']]) 65 | value std_err t_stat robust_std_err robust_t_stat 66 | Category Parameter 67 | LOS totcost -0.0048 0.0002 -20.3016 0.0003 -17.0773 68 | motorized_time -0.0173 0.0013 -13.6780 0.0014 -12.2046 69 | nonmotorized_time -0.0652 0.0053 -12.3235 0.0053 -12.2437 70 | Income hhinc#2,3 -0.0016 0.0014 -1.1304 0.0015 -1.0462 71 | hhinc#4 -0.0056 0.0018 -3.0211 0.0018 -3.1421 72 | hhinc#5 -0.0123 0.0052 -2.3445 0.0063 -1.9483 73 | hhinc#6 -0.0094 0.0031 -3.0885 0.0032 -2.9163 74 | ASCs ASC_BIKE -1.7748 0.3232 -5.4910 0.3693 -4.8052 75 | ASC_SR2 -2.3642 0.0969 -24.4032 0.1055 -22.4135 76 | ASC_SR3P -3.7992 0.1222 -31.0899 0.1276 -29.7836 77 | ASC_TRAN -0.5270 0.1479 -3.5636 0.1469 -3.5866 78 | ASC_WALK 0.4292 0.2526 1.6989 0.2583 1.6613 79 | -------------------------------------------------------------------------------- /larch/doc/example/009_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ================================================ 4 | 9: MTC MNL Mode Choice, TTR = 4.0 5 | ================================================ 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | Model 9 is formulated the same way as Model 8, only with the TTR set to 4. (`pp. 114 `_) 18 | 19 | .. testcode:: 20 | 21 | d = larch.examples.MTC() 22 | m = larch.Model(dataservice=d) 23 | 24 | .. testcode:: 25 | 26 | from larch.roles import P, X, PX 27 | m.utility_co[2] = P("ASC_SR2") + P("hhinc#2,3") * X("hhinc") 28 | m.utility_co[3] = P("ASC_SR3P") + P("hhinc#2,3") * X("hhinc") 29 | m.utility_co[4] = P("ASC_TRAN") + P("hhinc#4") * X("hhinc") 30 | m.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 31 | m.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 32 | 33 | .. testcode:: 34 | 35 | m.utility_ca = ( 36 | + P("nonmotorized_time") * X("(altnum>4) * tottime") 37 | + P("motorized_time") * (X("(altnum <= 4) * ivtt") 38 | + 4 * X("(altnum <= 4) * ovtt")) + PX("totcost") 39 | ) 40 | 41 | .. testcode:: 42 | 43 | m.availability_var = '_avail_' 44 | m.choice_ca_var = '_choice_' 45 | 46 | .. testcode:: 47 | 48 | m.ordering = ( 49 | ("LOS", ".*cost.*", ".*time.*", ".*ivtt.*", ), 50 | ("Income", "hhinc.*", ), 51 | ("ASCs", "ASC.*", ), 52 | ) 53 | 54 | .. doctest:: 55 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 56 | 57 | >>> m.load_data() 58 | >>> m.maximize_loglike() 59 | ┣ ...Optimization terminated successfully... 60 | >>> m.calculate_parameter_covariance() 61 | >>> m.loglike() 62 | -3590.916... 63 | 64 | >>> print(m.pfo()[['value','std_err','t_stat','robust_std_err','robust_t_stat']]) 65 | value std_err t_stat robust_std_err robust_t_stat 66 | Category Parameter 67 | LOS totcost -0.0048 0.0002 -20.3016 0.0003 -17.0773 68 | motorized_time -0.0173 0.0013 -13.6780 0.0014 -12.2046 69 | nonmotorized_time -0.0652 0.0053 -12.3235 0.0053 -12.2437 70 | Income hhinc#2,3 -0.0016 0.0014 -1.1304 0.0015 -1.0462 71 | hhinc#4 -0.0056 0.0018 -3.0211 0.0018 -3.1421 72 | hhinc#5 -0.0123 0.0052 -2.3445 0.0063 -1.9483 73 | hhinc#6 -0.0094 0.0031 -3.0885 0.0032 -2.9163 74 | ASCs ASC_BIKE -1.7748 0.3232 -5.4910 0.3693 -4.8052 75 | ASC_SR2 -2.3642 0.0969 -24.4032 0.1055 -22.4135 76 | ASC_SR3P -3.7992 0.1222 -31.0899 0.1276 -29.7836 77 | ASC_TRAN -0.5270 0.1479 -3.5636 0.1469 -3.5866 78 | ASC_WALK 0.4292 0.2526 1.6989 0.2583 1.6613 -------------------------------------------------------------------------------- /larch/util/activitysim/tour_mode_choice.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import yaml 5 | from typing import Collection 6 | from .. import Dict 7 | from pathlib import Path 8 | 9 | from .general import ( 10 | remove_apostrophes, 11 | construct_nesting_tree, 12 | linear_utility_from_spec, 13 | explicit_value_parameters, 14 | apply_coefficients, 15 | clean_values, 16 | simple_simulate_data, 17 | ) 18 | from ... import Model, DataFrames, P, X 19 | 20 | 21 | 22 | def tour_mode_choice_model( 23 | edb_directory="output/estimation_data_bundle/{name}/", 24 | return_data=False, 25 | ): 26 | data = simple_simulate_data( 27 | name="tour_mode_choice", 28 | edb_directory=edb_directory, 29 | ) 30 | coefficients = data.coefficients 31 | coef_template = data.coef_template 32 | spec = data.spec 33 | chooser_data = data.chooser_data 34 | settings = data.settings 35 | 36 | chooser_data = clean_values( 37 | chooser_data, 38 | data.alt_names, 39 | alt_names_to_codes=data.alt_names_to_codes, 40 | choice_code='override_choice_code', 41 | ) 42 | 43 | tree = construct_nesting_tree(data.alt_names, settings['NESTS']) 44 | 45 | purposes = list(coef_template.columns) 46 | 47 | # Setup purpose specific models 48 | m = {purpose: Model(graph=tree) for purpose in purposes} 49 | for alt_code, alt_name in tree.elemental_names().items(): 50 | # Read in base utility function for this alt_name 51 | u = linear_utility_from_spec( 52 | spec, 53 | x_col='Label', 54 | p_col=alt_name, 55 | ignore_x=('#',), 56 | ) 57 | for purpose in purposes: 58 | # Modify utility function based on template for purpose 59 | u_purp = sum( 60 | ( 61 | P(coef_template[purpose].get(i.param, i.param)) 62 | * i.data * i.scale 63 | ) 64 | for i in u 65 | ) 66 | m[purpose].utility_co[alt_code] = u_purp 67 | 68 | for model in m.values(): 69 | explicit_value_parameters(model) 70 | apply_coefficients(coefficients, m) 71 | 72 | avail = {} 73 | for acode, aname in data.alt_codes_to_names.items(): 74 | unavail_cols = list( 75 | (chooser_data[i.data] if i.data in chooser_data else chooser_data.eval(i.data)) 76 | for i in m[purposes[0]].utility_co[acode] 77 | if i.param == "-999" 78 | ) 79 | if len(unavail_cols): 80 | avail[acode] = sum(unavail_cols) == 0 81 | else: 82 | avail[acode] = 1 83 | avail = pd.DataFrame(avail).astype(np.int8) 84 | avail.index = chooser_data.index 85 | 86 | d = DataFrames( 87 | co=chooser_data, 88 | av=avail, 89 | alt_codes=data.alt_codes, 90 | alt_names=data.alt_names, 91 | ) 92 | 93 | for purpose, model in m.items(): 94 | model.dataservice = d.selector_co(f"tour_type=='{purpose}'") 95 | model.choice_co_code = 'override_choice_code' 96 | 97 | from larch.model.model_group import ModelGroup 98 | mg = ModelGroup(m.values()) 99 | 100 | if return_data: 101 | return mg, Dict( 102 | edb_directory=Path(edb_directory), 103 | chooser_data=chooser_data, 104 | avail=avail, 105 | coefficients=coefficients, 106 | coef_template=coef_template, 107 | spec=spec, 108 | ) 109 | 110 | return mg -------------------------------------------------------------------------------- /book/example/legacy/002_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ============================================ 4 | 2: MTC MNL Mode Choice, Motorized 5 | ============================================ 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | Model 2 posits that the effect of income relative to drive alone is the same for both 18 | shared ride modes and transit but is different for the other modes. 19 | This is represented in the model by constraining the income coefficients in both 20 | shared ride modes and the transit mode to be equal. (`pp. 108 `_) 21 | 22 | To accomplish this, we give the income parameters for all three alternatives the same name. 23 | 24 | .. testcode:: 25 | 26 | d = larch.examples.MTC() 27 | m = larch.Model(dataservice=d) 28 | 29 | .. testcode:: 30 | 31 | from larch.roles import P, X, PX 32 | m.utility_co[2] = P("ASC_SR2") + P("hhinc#Moto") * X("hhinc") 33 | m.utility_co[3] = P("ASC_SR3P") + P("hhinc#Moto") * X("hhinc") 34 | m.utility_co[4] = P("ASC_TRAN") + P("hhinc#Moto") * X("hhinc") 35 | m.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 36 | m.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 37 | 38 | .. testcode:: 39 | 40 | m.utility_ca = PX("tottime") + PX("totcost") 41 | 42 | .. testcode:: 43 | 44 | m.availability_var = '_avail_' 45 | m.choice_ca_var = '_choice_' 46 | 47 | .. testcode:: 48 | 49 | m.ordering = ( 50 | ("LOS", "totcost", "tottime", ), 51 | ("Income", "hhinc.*", ), 52 | ("ASCs", "ASC.*", ), 53 | ) 54 | 55 | .. testcode:: 56 | 57 | m.title = "MTC Example 2, Motorized" 58 | 59 | .. doctest:: 60 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 61 | 62 | >>> m.load_data() 63 | >>> m.maximize_loglike() 64 | ┣ ...Optimization terminated successfully... 65 | >>> m.calculate_parameter_covariance() 66 | >>> m.loglike() 67 | -3628.285... 68 | 69 | .. doctest:: 70 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 71 | 72 | >>> print(m.pfo()[['value','std_err','t_stat','robust_std_err','robust_t_stat']]) 73 | value std_err t_stat robust_std_err robust_t_stat 74 | Category Parameter 75 | LOS totcost -0.0049 0.0002 -20.5689 0.0003 -17.2879 76 | tottime -0.0514 0.0031 -16.6075 0.0035 -14.8957 77 | Income hhinc#5 -0.0125 0.0053 -2.3500 0.0066 -1.9071 78 | hhinc#6 -0.0092 0.0030 -3.0658 0.0032 -2.8985 79 | hhinc#Moto -0.0029 0.0012 -2.3483 0.0012 -2.3163 80 | ASCs ASC_BIKE -2.3903 0.3043 -7.8545 0.3604 -6.6321 81 | ASC_SR2 -2.1370 0.0884 -24.1778 0.0917 -23.2969 82 | ASC_SR3P -3.5322 0.1153 -30.6408 0.1176 -30.0402 83 | ASC_TRAN -0.7995 0.1124 -7.1099 0.1117 -7.1582 84 | ASC_WALK -0.2297 0.1933 -1.1879 0.2051 -1.1197 85 | -------------------------------------------------------------------------------- /larch/doc/example/002_mtc.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: larch 2 | 3 | ============================================ 4 | 2: MTC MNL Mode Choice, Motorized 5 | ============================================ 6 | 7 | .. testsetup:: * 8 | 9 | import larch 10 | import larch.examples 11 | import pandas 12 | pandas.set_option('display.max_columns',999) 13 | pandas.set_option('expand_frame_repr',False) 14 | pandas.set_option('display.precision',4) 15 | larch._doctest_mode_ = True 16 | 17 | Model 2 posits that the effect of income relative to drive alone is the same for both 18 | shared ride modes and transit but is different for the other modes. 19 | This is represented in the model by constraining the income coefficients in both 20 | shared ride modes and the transit mode to be equal. (`pp. 108 `_) 21 | 22 | To accomplish this, we give the income parameters for all three alternatives the same name. 23 | 24 | .. testcode:: 25 | 26 | d = larch.examples.MTC() 27 | m = larch.Model(dataservice=d) 28 | 29 | .. testcode:: 30 | 31 | from larch.roles import P, X, PX 32 | m.utility_co[2] = P("ASC_SR2") + P("hhinc#Moto") * X("hhinc") 33 | m.utility_co[3] = P("ASC_SR3P") + P("hhinc#Moto") * X("hhinc") 34 | m.utility_co[4] = P("ASC_TRAN") + P("hhinc#Moto") * X("hhinc") 35 | m.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 36 | m.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 37 | 38 | .. testcode:: 39 | 40 | m.utility_ca = PX("tottime") + PX("totcost") 41 | 42 | .. testcode:: 43 | 44 | m.availability_var = '_avail_' 45 | m.choice_ca_var = '_choice_' 46 | 47 | .. testcode:: 48 | 49 | m.ordering = ( 50 | ("LOS", "totcost", "tottime", ), 51 | ("Income", "hhinc.*", ), 52 | ("ASCs", "ASC.*", ), 53 | ) 54 | 55 | .. testcode:: 56 | 57 | m.title = "MTC Example 2, Motorized" 58 | 59 | .. doctest:: 60 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 61 | 62 | >>> m.load_data() 63 | >>> m.maximize_loglike() 64 | ┣ ...Optimization terminated successfully... 65 | >>> m.calculate_parameter_covariance() 66 | >>> m.loglike() 67 | -3628.285... 68 | 69 | .. doctest:: 70 | :options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF 71 | 72 | >>> print(m.pfo()[['value','std_err','t_stat','robust_std_err','robust_t_stat']]) 73 | value std_err t_stat robust_std_err robust_t_stat 74 | Category Parameter 75 | LOS totcost -0.0049 0.0002 -20.5689 0.0003 -17.2879 76 | tottime -0.0514 0.0031 -16.6075 0.0035 -14.8957 77 | Income hhinc#5 -0.0125 0.0053 -2.3500 0.0066 -1.9071 78 | hhinc#6 -0.0092 0.0030 -3.0658 0.0032 -2.8985 79 | hhinc#Moto -0.0029 0.0012 -2.3483 0.0012 -2.3163 80 | ASCs ASC_BIKE -2.3903 0.3043 -7.8545 0.3604 -6.6321 81 | ASC_SR2 -2.1370 0.0884 -24.1778 0.0917 -23.2969 82 | ASC_SR3P -3.5322 0.1153 -30.6408 0.1176 -30.0402 83 | ASC_TRAN -0.7995 0.1124 -7.1099 0.1117 -7.1582 84 | ASC_WALK -0.2297 0.1933 -1.1879 0.2051 -1.1197 85 | -------------------------------------------------------------------------------- /tests/test_model_group.py: -------------------------------------------------------------------------------- 1 | import larch 2 | import pandas as pd 3 | from larch import P,X,PX 4 | from pytest import approx 5 | from larch.data_warehouse import example_file 6 | 7 | def test_simple_model_group(): 8 | 9 | df = pd.read_csv(example_file("MTCwork.csv.gz")) 10 | df.set_index(['casenum','altnum'], inplace=True) 11 | d = larch.DataFrames(df, ch='chose', crack=True) 12 | d.set_alternative_names({ 13 | 1: 'DA', 14 | 2: 'SR2', 15 | 3: 'SR3+', 16 | 4: 'Transit', 17 | 5: 'Bike', 18 | 6: 'Walk', 19 | }) 20 | 21 | m0 = larch.Model(dataservice=d) 22 | m0.utility_co[2] = P("ASC_SR2") + P("hhinc#2") * X("hhinc") 23 | m0.utility_co[3] = P("ASC_SR3P") + P("hhinc#3") * X("hhinc") 24 | m0.utility_co[4] = P("ASC_TRAN") + P("hhinc#4") * X("hhinc") 25 | m0.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 26 | m0.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 27 | m0.utility_ca = ( 28 | (P("tottime_m")*X("tottime") + P("totcost_m")*X("totcost"))*X("femdum == 0") 29 | + 30 | (P("tottime_f")*X("tottime") + P("totcost_f")*X("totcost"))*X("femdum == 1") 31 | ) 32 | 33 | m1 = larch.Model(dataservice=d.selector_co("femdum == 0")) 34 | m1.utility_co[2] = P("ASC_SR2") + P("hhinc#2") * X("hhinc") 35 | m1.utility_co[3] = P("ASC_SR3P") + P("hhinc#3") * X("hhinc") 36 | m1.utility_co[4] = P("ASC_TRAN") + P("hhinc#4") * X("hhinc") 37 | m1.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 38 | m1.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 39 | m1.utility_ca = P("tottime_m")*X("tottime") + P("totcost_m")*X("totcost") 40 | 41 | m2 = larch.Model(dataservice=d.selector_co("femdum == 1")) 42 | m2.utility_co[2] = P("ASC_SR2") + P("hhinc#2") * X("hhinc") 43 | m2.utility_co[3] = P("ASC_SR3P") + P("hhinc#3") * X("hhinc") 44 | m2.utility_co[4] = P("ASC_TRAN") + P("hhinc#4") * X("hhinc") 45 | m2.utility_co[5] = P("ASC_BIKE") + P("hhinc#5") * X("hhinc") 46 | m2.utility_co[6] = P("ASC_WALK") + P("hhinc#6") * X("hhinc") 47 | m2.utility_ca = P("tottime_f")*X("tottime") + P("totcost_f")*X("totcost") 48 | 49 | m0.load_data() 50 | assert m0.loglike2().ll == approx(-7309.600971749625) 51 | 52 | m1.load_data() 53 | assert m1.loglike2().ll == approx(-4068.8091617468717) 54 | 55 | m2.load_data() 56 | assert m2.loglike2().ll == approx(-3240.7918100027578) 57 | 58 | from larch.model.model_group import ModelGroup 59 | 60 | mg = ModelGroup([m1,m2]) 61 | 62 | assert mg.loglike2().ll == approx(-7309.600971749625) 63 | assert mg.loglike() == approx(-7309.600971749625) 64 | 65 | pd.testing.assert_series_equal( 66 | mg.loglike2().dll.sort_index(), m0.loglike2().dll.sort_index() 67 | ) 68 | 69 | m0.simple_step_bhhh() 70 | mg.set_values(**m0.pf.value) 71 | 72 | pd.testing.assert_series_equal( 73 | mg.loglike2().dll.sort_index(), m0.loglike2().dll.sort_index() 74 | ) 75 | 76 | assert mg.loglike2().ll == approx(-4926.4822036792275) 77 | assert mg.check_d_loglike().data.similarity.min() > 4 78 | 79 | result = mg.maximize_loglike(method='slsqp') 80 | assert result.loglike == approx(-3620.697668335103) 81 | 82 | mg2 = ModelGroup([]) 83 | mg2.append(m1) 84 | mg2.append(m2) 85 | assert mg2.loglike() == approx(-3620.697667552756) 86 | 87 | mg3 = ModelGroup([]) 88 | mg3.append(m1) 89 | mg3.append(m2) 90 | mg3.doctor() 91 | assert mg3.loglike() == approx(-3620.697667552756) 92 | -------------------------------------------------------------------------------- /larch/log/normal.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | from contextlib import contextmanager 4 | import time 5 | from ..util.timesize import timesize_stack 6 | 7 | FILE_LOG_FORMAT = '%(name)s.%(levelname)s: %(message)s' 8 | CONSOLE_LOG_FORMAT = '[%(asctime)s] %(name)s.%(levelname)s: %(message)s' 9 | 10 | DEFAULT_LOG_LEVEL = logging.WARNING 11 | 12 | from . import logger_name 13 | 14 | def log_to_console(level=None): 15 | 16 | if level is None: 17 | level = DEFAULT_LOG_LEVEL 18 | 19 | logger = logging.getLogger(logger_name) 20 | 21 | # avoid creation of multiple stream handlers for logging to console 22 | for entry in logger.handlers: 23 | if (isinstance(entry, logging.StreamHandler)) and (entry.formatter._fmt == CONSOLE_LOG_FORMAT): 24 | return logger 25 | 26 | console_handler = logging.StreamHandler(stream=sys.stdout) 27 | console_handler.setLevel(level) 28 | console_handler.setFormatter(logging.Formatter(CONSOLE_LOG_FORMAT)) 29 | logger.addHandler(console_handler) 30 | if level < logger.getEffectiveLevel(): 31 | logger.setLevel(level) 32 | 33 | return logger 34 | 35 | 36 | def log_to_file(filename, level=None): 37 | 38 | if level is None: 39 | level = DEFAULT_LOG_LEVEL 40 | 41 | logger = logging.getLogger(logger_name) 42 | 43 | # avoid creation of multiple file handlers for logging to the same file 44 | for entry in logger.handlers: 45 | if (isinstance(entry, logging.FileHandler)) and (entry.baseFilename == filename): 46 | return logger 47 | 48 | file_handler = logging.FileHandler(filename) 49 | file_handler.setLevel(level) 50 | file_handler.setFormatter(logging.Formatter(FILE_LOG_FORMAT)) 51 | logger.addHandler(file_handler) 52 | 53 | return logger 54 | 55 | 56 | logger = log = log_to_console() 57 | 58 | 59 | @contextmanager 60 | def timing_log(label=''): 61 | start_time = time.time() 62 | log.critical(f"